После того, как встретил на просторах интернета задачу "BuzzFizz", захотел решить её на 1С. На Инфостарт есть поиск самого короткого решения, давайте теперь попробуем найти самое быстрое!
Напомню текст задачи:
Необходимо написать программу, которая выводит на экран числа от 1 до 100. При этом вместо чисел, кратных трем, программа должна выводить слово Fizz, а вместо чисел, кратных пяти — слово Buzz. Если число кратно трем и кратна пяти, то программа должна выводить слово FizzBuzz.
Для того, чтобы замеры были более показательными, я увеличил количество выводимых чисел до 100 000 и установил это значение в переменную "Лимит".
Лимит = 100000;
Итерация 1
Первая функция выполнялась за 1,701917 секунд - думал, что без "ИначеЕсли" будет быстрее.
Сообщение = Новый СообщениеПользователю;
Для Счетчик = 1 По Лимит Цикл
Текст = "";
Если Счетчик % 3 = 0 Тогда
Текст = "Fizz";
КонецЕсли;
Если Счетчик % 5 = 0 Тогда
Текст = Текст + "Buzz";
КонецЕсли;
Если Текст = "" Тогда
Текст = Счетчик;
КонецЕсли;
Сообщение.Текст = Текст;
Сообщение.Сообщить();
КонецЦикла;
Итерация 2
Но нет, я ошибался. Цикл с "ИначеЕсли" выполнился за 1,428153 с.
Сообщение = Новый СообщениеПользователю;
Для Счетчик = 1 По Лимит Цикл
Если Счетчик % 3 = 0 И Счетчик % 5 = 0 Тогда
Текст = "FizzBuzz";
ИначеЕсли Счетчик % 3 = 0 Тогда
Текст = "Fizz";
ИначеЕсли Счетчик % 5 = 0 Тогда
Текст = "Buzz";
Иначе
Текст = Счетчик;
КонецЕсли;
Сообщение.Текст = Текст;
Сообщение.Сообщить();
КонецЦикла;
Кстати, если заменить первое условие на "Счетчик % 15 = 0", то прироста производительности не происходит.
Итерация 3
А что, если заранее определить, кратно ли число 3 или 5? Получаем увеличение времени выполнения до 1,569277 с.
Сообщение = Новый СообщениеПользователю;
Для Счетчик = 1 По Лимит Цикл
КратенТрём = Счетчик % 3 = 0;
КратенПяти = Счетчик % 5 = 0;
Если КратенТрём И КратенПяти Тогда
Текст = "FizzBuzz";
ИначеЕсли КратенТрём Тогда
Текст = "Fizz";
ИначеЕсли КратенПяти Тогда
Текст = "Buzz";
Иначе
Текст = Счетчик;
КонецЕсли;
Сообщение.Текст = Текст;
Сообщение.Сообщить();
КонецЦикла;
Итерация 4
Понятно, пойдём другим путём - слегка уменьшим количество сравнений.
Сообщение = Новый СообщениеПользователю;
Для Счетчик = 1 По Лимит Цикл
Если Счетчик % 3 = 0 Тогда
Если Счетчик % 5 = 0 Тогда
Текст = "FizzBuzz";
Иначе
Текст = "Fizz";
КонецЕсли;
ИначеЕсли Счетчик % 5 = 0 Тогда
Текст = "Buzz";
Иначе
Текст = Счетчик;
КонецЕсли;
Сообщение.Текст = Текст;
Сообщение.Сообщить();
КонецЦикла;
Да, так лучше - 1,354212 с. Но подождите, почему мы используем объект СообщениеПользователю? Ведь самая долгая операция в нашем алгоритме - это вывод. Значит, нужно и её оптимизировать. Переделаем на "Сообщить(Текст);" и уменьшим время выполнения до 1,255155 с.
Можно ещё уменьшить количество проверок Если Тогда - проверять сначала остаток от деления на 5. Тогда мы будем попадать во вложенную проверку каждую пятую, а не каждую третью итерацию.
Если Счетчик % 5 = 0 Тогда
Если Счетчик % 3 = 0 Тогда
Текст = "FizzBuzz";
Иначе
Текст = "Buzz";
КонецЕсли;
ИначеЕсли Счетчик % 3 = 0 Тогда
Текст = "Fizz";
Иначе
Текст = Счетчик;
КонецЕсли;
Получается время выполнения 1,233862 секунды = выигрываем 0.021с.
UPD: Протестировал цикл без операций остатка на деление, как предложил CheBurator в комментариях, а увеличивать переменную.
К сожалению, код замедлился - 1,461621 секунды, если прибавлять к отрицательному числу и 1,442719, если прибавлять к нулю.
Итерация 5
Хорошо, процедуру оптимизировали, идеи кончились. Дальше будем оптимизировать вывод.
На всякий случай попробуем собрать одну строку текста и вывести её - скорее всего это будет дольше, но почему бы и нет.
ТекстСообщения = "";
Для Счетчик = 1 По Лимит Цикл
// ...
ТекстСообщения = ТекстСообщения + Символы.ПС + Текст;
// ...
КонецЦикла;
Сообщить(ТекстСообщения);
И получаем жуткие 26 секунд.
Ладно. Пойдём другим путём. Каждые 15 раз мы выводим одинаковое количество Fizz и Buzz. Так почему бы их заранее не определить?
КоличествоВыводовЦелого = Цел(Лимит / 15);
Остаток = Лимит % 15;
Массив = Новый Массив;
Массив.Добавить(1);
Массив.Добавить(2);
Массив.Добавить("Fizz");
Массив.Добавить(4);
Массив.Добавить("Buzz");
Массив.Добавить("Fizz");
Массив.Добавить(7);
Массив.Добавить(8);
Массив.Добавить("Fizz");
Массив.Добавить("Buzz");
Массив.Добавить(11);
Массив.Добавить("Fizz");
Массив.Добавить(13);
Массив.Добавить(14);
Массив.Добавить("FizzBuzz");
Для Счетчик = 0 По КоличествоВыводовЦелого Цикл
Для Индекс = 0 По 14 Цикл
Сообщить(Массив[Индекс]);
Если ТипЗнч(Массив[Индекс]) = Тип("Число") Тогда
Массив[Индекс] = Массив[Индекс] + 15;
КонецЕсли;
КонецЦикла;
КонецЦикла;
ПоследнийЭлемент = Лимит - Остаток;
// Для остатка выполняем старый алгоритм
Для Счетчик = ПоследнийЭлемент По Лимит Цикл
Если Счетчик % 3 = 0 Тогда
Если Счетчик % 5 = 0 Тогда
Текст = "FizzBuzz";
Иначе
Текст = "Fizz";
КонецЕсли;
ИначеЕсли Счетчик % 5 = 0 Тогда
Текст = "Buzz";
Иначе
Текст = Счетчик;
КонецЕсли;
Сообщить(Текст);
КонецЦикла;
Окей, уже лучше, 1,189597 секунд, но что-то мне не нравится, что мы каждый раз делаем проверку на тип значения.
Итерация 6
Давайте сразу в цикле увеличивать все числа массива на 15.
Для Счетчик = 0 По КоличествоВыводовЦелого Цикл
Для Индекс = 0 По 14 Цикл
Сообщить(Массив[Индекс]);
КонецЦикла;
Массив[0] = Массив[0] + 15;
Массив[1] = Массив[1] + 15;
Массив[3] = Массив[3] + 15;
Массив[6] = Массив[6] + 15;
Массив[7] = Массив[7] + 15;
Массив[10] = Массив[10] + 15;
Массив[12] = Массив[12] + 15;
Массив[14] = Массив[14] + 15;
КонецЦикла;
Отлично! Впервые время меньше секунды - 0,985075.
Итерация 7. Финал
По-прежнему вывод на экран - самая долгая процедура. Тогда давайте выводить сообщение не 15 раз за цикл, а всего одно!
Для Счетчик = 0 По КоличествоВыводовЦелого Цикл
Сообщить(СтрСоединить(Массив, Символы.ПС));
Массив[0] = Массив[0] + 15;
Массив[1] = Массив[1] + 15;
Массив[3] = Массив[3] + 15;
Массив[6] = Массив[6] + 15;
Массив[7] = Массив[7] + 15;
Массив[10] = Массив[10] + 15;
Массив[12] = Массив[12] + 15;
Массив[14] = Массив[14] + 15;
КонецЦикла;
Вуаля! Получаем 0,143189 секунд!
КоличествоВыводовЦелого = Цел(Лимит / 15);
Остаток = Лимит % 15;
Массив = Новый Массив;
Массив.Добавить(1);
Массив.Добавить(2);
Массив.Добавить("Fizz");
Массив.Добавить(4);
Массив.Добавить("Buzz");
Массив.Добавить("Fizz");
Массив.Добавить(7);
Массив.Добавить(8);
Массив.Добавить("Fizz");
Массив.Добавить("Buzz");
Массив.Добавить(11);
Массив.Добавить("Fizz");
Массив.Добавить(13);
Массив.Добавить(14);
Массив.Добавить("FizzBuzz");
Для Счетчик = 1 По КоличествоВыводовЦелого Цикл
Сообщить(СтрСоединить(Массив, Символы.ПС));
Массив[0] = Массив[0] + 15;
Массив[1] = Массив[1] + 15;
Массив[3] = Массив[3] + 15;
Массив[6] = Массив[6] + 15;
Массив[7] = Массив[7] + 15;
Массив[10] = Массив[10] + 15;
Массив[12] = Массив[12] + 15;
Массив[13] = Массив[13] + 15;
КонецЦикла;
// Можно и этот блок заменить на вывод массива
ПоследнийЭлемент = Лимит - Остаток;
Для Счетчик = ПоследнийЭлемент По Лимит Цикл
Если Счетчик % 5 = 0 Тогда
Если Счетчик % 3 = 0 Тогда
Текст = "FizzBuzz";
Иначе
Текст = "Buzz";
КонецЕсли;
ИначеЕсли Счетчик % 3 = 0 Тогда
Текст = "Fizz";
Иначе
Текст = Счетчик;
КонецЕсли;
Сообщить(Текст);
КонецЦикла;
Итоги
Вот так мы получили прирост производительности в 12 раз. Даже получившийся код можно оптимизировать - избавиться от проверки остатка от деления на 3 и 5 и выводить только необходимую часть массива. Что скажете друзья? Как бы написали вы? Очень жду ваши варианты в комментариях!
Бонус
По предложению vandalsvq, выполнил замер из статьи FizzBuzz на 1С. Чем короче, тем веселее.
Вариант 1 - в лоб: 1,540015
Вариант 2 - сократим ИначеЕсли: 1,848520
Вариант 3 - заменим Если на ?: 1,314094
Вариант 4 - короче, не значит лучше: 1,192415
Честно говоря, я не ожидал. Корче - значит лучше :)
Бонус 2
nomadon прислал ссылку на Хабр "FizzBuzz по-сениорски" - там подсмотрел идею со СтрШаблон. Не думал, что это будет работать быстрее, но снова был приятно удивлён - результат оказался 0,085349 секунд! А это ускорение уже почти в 20 раз!
И, как справедливо заметили в комментариях это мы ещё не использовали многопоточность!