Самый быстрый 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    3162    stopa85    12    

38

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

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

19.10.2023    7550    user1959478    51    

36

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

Расширение (+ обработка) представляют собою математический тренажер. Ваш ребенок сможет проверить свои знание на математические вычисление до 100.

2 стартмани

29.09.2023    3107    maksa2005    8    

26

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

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

1 стартмани

09.06.2023    10902    7    SpaceOfMyHead    18    

61

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

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

03.04.2023    4357    RustIG    9    

25

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

В статье анализируются средства платформы для решения системы линейных уравнений в 1С. Приводятся доводы в пользу некорректной работы встроенных алгоритмов, а значит потенциально некорректного расчета себестоимости в типовых конфигурациях.

23.11.2022    3527    gzharkoj    14    

25

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

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

1 стартмани

21.03.2022    9041    7    kalyaka    11    

44
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. nomadon 369 03.02.21 10:07 Сейчас в теме
6. ardn 657 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 3416 03.02.21 10:18 Сейчас в теме
Явно многопоточности не хватает.
3. vandalsvq 1587 03.02.21 10:45 Сейчас в теме
Вот почитай на досуге варианты, вдруг будет не лень погонять https://infostart.ru/1c/articles/1097535/
8. Donrad 11 03.02.21 21:05 Сейчас в теме
(3)Погонял. Оказалось, чем короче, тем быстрее :)
Добавил результаты в статью.
4. CheBurator 2712 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 744 03.02.21 12:30 Сейчас в теме
А как делались замеры?
10. Donrad 11 03.02.21 21:09 Сейчас в теме
(5)Замеры выполнялись штатным функционалом "Замер производительности" в файловой базе в тонком клиенте.
Код запускался во внешней обработке. На каждый замер по четыре прогона - один "в холостую" и три "боевых", из которых вычислялось среднее арифметическое время.
16. awk 744 04.02.21 09:26 Сейчас в теме
(10) Поздравляю, у вас искаженные замеры. Не знаю насколько это влияет на общий результат, но так замеры делать нельзя. Т.к. штатный "Замер производительности" работает через отладчик, что вносит большие искажения, на малых объемах кода.
17. Donrad 11 04.02.21 09:53 Сейчас в теме
(16)Понял, спасибо. А как правильно сделать замер? Через подсистему БСП?
18. awk 744 04.02.21 09:54 Сейчас в теме
(17) Я часа через два скину обработку....
19. awk 744 04.02.21 10:24 Сейчас в теме
(17) Прикрепил обработку с двумя реализациями. Никаких секунд выполнения по "классике" не обнаружил (файловая база).
Прикрепленные файлы:
Обработка1.epf
20. Donrad 11 04.02.21 10:33 Сейчас в теме
(19)Ого, да, большое спасибо! Полагаю, в таком случае секунды в статье можно смело переименовывать в попугаев :)
Но, думаю, перезамерять не имеет смысла - общая концепция времени выполнения сохраняется.
Но за обработку правда большое спасибо!
21. awk 744 04.02.21 10:49 Сейчас в теме
(20) Попробовал, в режиме отладки, на тех же значениях. У меня 1С уже 15 минут не отвечает.... Можно писать статью: "Почему на рабочих базах нельзя включать режим отладки". Вообще был бы интересен сравнительный график алгоритмов, время выполнения от количества итераций.
22. awk 744 04.02.21 11:06 Сейчас в теме
(20) Нет, замер в режиме отладки и без искажен достаточно сильно. См. скриншет.
Прикрепленные файлы:
12. artbear 1563 04.02.21 00:09 Сейчас в теме
(0) В блоке с СтрШаблон в финальном цикле ошибка

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

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