Самый быстрый FizzBuzz на 1С

03.02.21

Разработка - Математика и алгоритмы

Давайте попробуем найти самое быстрое решение задачи "BuzzFizz" на 1С.

После того, как встретил на просторах интернета задачу "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. Так почему бы их заранее не определить?

 
 Версия на итерации 5

 

КоличествоВыводовЦелого = Цел(Лимит / 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 раз!

 
 Код со СтрШаблон

 

И, как справедливо заметили в комментариях это мы ещё не использовали многопоточность!

См. также

Метод Дугласа-Пойкера для эффективного хранения метрик

Математика и алгоритмы Платформа 1C v8.2 Конфигурации 1cv8 Россия Абонемент ($m)

На написание данной работы меня вдохновила работа @glassman «Переход на ClickHouse для анализа метрик». Автор анализирует большой объем данных, много миллионов строк, и убедительно доказывает, что ClickHouse справляется лучше PostgreSQL. Я же покажу как можно сократить объем данных в 49.9 раз при этом: 1. Сохранить значения локальных экстремумов 2. Отклонения от реальных значений имеют наперед заданную допустимую погрешность.

1 стартмани

30.01.2024    1919    stopa85    12    

34

Алгоритм симплекс-метода для решения задачи раскроя

Математика и алгоритмы Бесплатно (free)

Разработка алгоритма, построенного на модели симплекс-метода, для нахождения оптимального раскроя.

19.10.2023    4768    user1959478    50    

34

Регулярные выражения на 1С

Математика и алгоритмы Инструментарий разработчика Платформа 1С v8.3 Мобильная платформа Россия Абонемент ($m)

Что ж... лучше поздно, чем никогда. Подсистема 1С для работы с регулярными выражениями: разбор выражения, проверка на соответствие шаблону, поиск вхождений в тексте.

1 стартмани

09.06.2023    7785    5    SpaceOfMyHead    17    

56

Мини-обзор разных решений задач

Математика и алгоритмы Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Три задачи - три идеи - три решения. Мало кода, много смысла. Мини-статья.

03.04.2023    3145    RustIG    6    

25

Модель распределения суммы по базе

Математика и алгоритмы Платформа 1С v8.3 Россия Абонемент ($m)

Обычно под распределением понимают определение сумм пропорционально коэффициентам. Предлагаю включить сюда также распределение по порядку (FIFO, LIFO) и повысить уровень размерности до 2-х. 1-ое означает, что распределение может быть не только пропорциональным, но и по порядку, а 2-ое - это вариант реализации матричного распределения: по строкам и столбцам. Возможно вас заинтересует также необычное решение этой задачи через создание DSL на базе реализации текучего интерфейса

1 стартмани

21.03.2022    7982    7    kalyaka    11    

44

Изменения формата файлов конфигурации (CF) в 8.3.16

Математика и алгоритмы Платформа 1С v8.3 Бесплатно (free)

Дополнение по формату файлов конфигурации (*.cf) в версии 8.3.16.

16.12.2021    4595    fishca    13    

37

Интересная задача на Yandex cup 2021

Математика и алгоритмы Бесплатно (free)

Мое решение задачи на Yandex cup 2021 (frontend). Лабиринт. JavaScript.

12.10.2021    9003    John_d    73    

46
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. nomadon 367 03.02.21 10:07 Сейчас в теме
6. ardn 627 03.02.21 14:55 Сейчас в теме
(1) да, кстати
Понятно, что интринсинки не сможем сделать, но про многопоточность можно было и упомянуть, тем более, что в оригинальной статье она была.
7. Donrad 11 03.02.21 21:03 Сейчас в теме
(1) Спасибо за ссылку. Хм. Я создаю массив, а автор использует аналог "СтрШаблон".
Кажется, всё, что дальше по тексту на 1С мы не реализуем?
Я вдохновлялся комментариями к вот этой статье на Хабре. Но, к сожалению, не всё, как минимум, понял :D. Или не придумал, как повторить на 1С.
Добавил анализ с СтрШаблон. Это оказалось быстрее, спасибо.
11. Donrad 11 04.02.21 00:06 Сейчас в теме
2. PowerBoy 3364 03.02.21 10:18 Сейчас в теме
Явно многопоточности не хватает.
3. vandalsvq 1549 03.02.21 10:45 Сейчас в теме
Вот почитай на досуге варианты, вдруг будет не лень погонять https://infostart.ru/1c/articles/1097535/
8. Donrad 11 03.02.21 21:05 Сейчас в теме
(3)Погонял. Оказалось, чем короче, тем быстрее :)
Добавил результаты в статью.
4. CheBurator 3119 03.02.21 11:20 Сейчас в теме
Итоговый код м.б. ускорен скорее всего еще. Причем это сильнее будет заметно на больших дистанциях. X%5 - времяжрущая операция. поэтому остаток от деления если от него не избавляться а заменить на типа
СчОстДелНа5 = -5 в цикле делать СчОстДелНа5 = СчОстДелНа5+1 и Если СчОстДелНа5=0 Тогда....
9. Donrad 11 03.02.21 21:07 Сейчас в теме
(4)Оказалось, что нет. Остаток от деления (по крайней мере на 100 000 чисел) сработал быстрее. Но если использовать ваш метод, то тогда лучше использовать положительные значения - с ними будет быстрее.
Добавил результаты в статью.
5. awk 741 03.02.21 12:30 Сейчас в теме
А как делались замеры?
10. Donrad 11 03.02.21 21:09 Сейчас в теме
(5)Замеры выполнялись штатным функционалом "Замер производительности" в файловой базе в тонком клиенте.
Код запускался во внешней обработке. На каждый замер по четыре прогона - один "в холостую" и три "боевых", из которых вычислялось среднее арифметическое время.
16. awk 741 04.02.21 09:26 Сейчас в теме
(10) Поздравляю, у вас искаженные замеры. Не знаю насколько это влияет на общий результат, но так замеры делать нельзя. Т.к. штатный "Замер производительности" работает через отладчик, что вносит большие искажения, на малых объемах кода.
17. Donrad 11 04.02.21 09:53 Сейчас в теме
(16)Понял, спасибо. А как правильно сделать замер? Через подсистему БСП?
18. awk 741 04.02.21 09:54 Сейчас в теме
(17) Я часа через два скину обработку....
19. awk 741 04.02.21 10:24 Сейчас в теме
(17) Прикрепил обработку с двумя реализациями. Никаких секунд выполнения по "классике" не обнаружил (файловая база).
Прикрепленные файлы:
Обработка1.epf
20. Donrad 11 04.02.21 10:33 Сейчас в теме
(19)Ого, да, большое спасибо! Полагаю, в таком случае секунды в статье можно смело переименовывать в попугаев :)
Но, думаю, перезамерять не имеет смысла - общая концепция времени выполнения сохраняется.
Но за обработку правда большое спасибо!
21. awk 741 04.02.21 10:49 Сейчас в теме
(20) Попробовал, в режиме отладки, на тех же значениях. У меня 1С уже 15 минут не отвечает.... Можно писать статью: "Почему на рабочих базах нельзя включать режим отладки". Вообще был бы интересен сравнительный график алгоритмов, время выполнения от количества итераций.
22. awk 741 04.02.21 11:06 Сейчас в теме
(20) Нет, замер в режиме отладки и без искажен достаточно сильно. См. скриншет.
Прикрепленные файлы:
12. artbear 1527 04.02.21 00:09 Сейчас в теме
(0) В блоке с СтрШаблон в финальном цикле ошибка

Пока Счетчик <= Лимит Цикл

нужно Остаток вместе Лимит, верно?
14. Donrad 11 04.02.21 08:33 Сейчас в теме
(12) Не совсем. Значение переменной Остаток = 10 на этот момент, а переменной Счётчик = 999 990. Т.е. нам не нужно обнулять счётчик, иначе условие "вывести числа до 100 000" не будет выполнено - снова будут выведены числа 1..10.
Тут переменную Остаток вообще можно убрать.
13. artbear 1527 04.02.21 00:11 Сейчас в теме
(0) И еще идея - собирать в СтрШаблон не 15 позиций, а больше - 30, 45, 150, 1500 и т.п. )
15. Donrad 11 04.02.21 08:50 Сейчас в теме
(13)Не сыграем понял, что вы имели ввиду. Написать свой аналог СтрШаблон, который будет поддерживать 1500 параметров? :)
А как тогда понять, какой шаблон использовать, если переменная Лимит будет равна 16, например?
23. DrAku1a 1718 24.10.21 10:57 Сейчас в теме
Меня более всего поразило то, что использование массива и СтрСоединить() вместо конкатенации строк в цикле даёт значительный прирост производительности.
Хм... походу, предстоит переписать много своего кода...)) (того, что будет работать на 8.3.6 и выше)
Оставьте свое сообщение