Ползущий массив (сравнение скоростей)

Простейшие вопросы в области инженерной разработки
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Остапа несло...
Не могу понять, почему индексированный while практически не уступает по скорости for-у.
Предположил, что в первом тесте цикл сразу знает количество итераций, повторил тест с имитацией неизвестности - результат тот же, скорость не существенно ниже, чем у For... :shok:
У кого-нибудь есть идеи объяснения этого?
while2res.png
while2res.png (5.56 КБ) 2399 просмотров
while2.png
while2.png (10.12 КБ) 2399 просмотров
Юрий
leader
leader
Сообщения: 526
Зарегистрирован: 28 фев 2010, 18:04
Версия LabVIEW: LV2018
Благодарил (а): 10 раз
Поблагодарили: 18 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Юрий »

В определении начала кольца допустил неточность. Следует делать так:
ДвойнойМассив.png
Вложения
ДвойнойМассив.vi
(10.83 КБ) 55 скачиваний
Аватара пользователя
Jakob Brontfeyn

Activity Gold Silver Black
expert
expert
Сообщения: 1729
Зарегистрирован: 28 фев 2008, 11:01
Награды: 6
Благодарил (а): 1 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Jakob Brontfeyn »

Я для сложных экспериментов в лабораториях, чтобы долго не заморачиваться,
применяю метод с использованием Chathistory-массива
очень удобно, потому что, если надо, можно получить сразу несколько
синхронно ползучих массивов в одном чате. При этом сам чат-индикатор на панели,
в погоне за скоростью, есть возможность скрыть.
В тестовом примере сделал так, что массив из 10000 элементов
ползет 10000 раз, то бишь полностью обновляется.
Как получается, вам судить, смотрите два примера,
для одного и для трех ползучих массивов.
Вложения
polzet_odin_omassiv.vi
(35.97 КБ) 53 скачивания
polzet_tri_massiva.vi
(45.02 КБ) 60 скачиваний
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Jakob Brontfeyn писал(а): 13 дек 2021, 07:53 применяю метод с использованием Chathistory-массива
очень удобно, потому что, если надо, можно получить сразу несколько
синхронно ползучих массивов в одном чате.
вариант интересный, но скоростью совершенно не блещет. Во-первых, обращение к свойству элемента - очень медленная процедура, да ещё и на UI завязанная. А во-вторых, для получения фрагмента данных каждый раз придётся вытаскивать всю историю, чтобы потом кусок вырезать
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Chupakabra писал(а): 10 дек 2021, 15:27 Есть еще такая штука In-Memory SQLite. По скорости ничего не скажу, нужно пробовать. Но можно временные ряды хранить в памяти и извлекать со всеми плюшками sql.
Всё не давал покоя этот метод, решил его тоже протестировать. Тестировал отдельно, потому что очевиден проигрыш по скорости. Но вот удобство выборки соблазняло.
Главное опасение было - прожорливость метода - будет ли SQLite нормально освобождать память при удалении старых данных.
Тест: буфер на 10 часов, каждую секунду в базу добавляется новое значение, тут же читается 1 час истории из середины. И раз в 10 минут удаляется всё, что старше 10 часов.
Результаты порадовали. Явной потери памяти не наблюдается, в скорости вполне приличные - 10-12 мс на итерацию (запись + выборка). Полусекундные всплески - это удаление истории.
sqlite_speed.PNG
График занятой памяти
sqlite_mem.PNG
Исходный код:
sqlite_BD.png
Итого: при не строго тактированных данных метод вполне оправдан простотой выборки по времени.
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Озадачился вопросом утечек памяти.
Короткие (по часу) тесты не очень убедительны, прогнал 10-часовые.
Тест гонял на виртуальной машине, которая работает на основной машине, чтобы работать не мешало, а тесту никто память не засорял.
long_test.png
Результаты опять же не однозначные. Графики - скользящее среднее 50 шт
1) поворот-замена. Тут память скорее не утекает
001.PNG
2) удалить-вставить
тут непонятно, что за всплеск во второй половине, но в целом скорее утечек нет
002.PNG
3) удалить-прибавить (build array)
Вполне убедительно, что память не исчезает
003.PNG
4) split-build
Тоже непонятный всплеск, но два плато.
004.PNG
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Итого.
Надоело гонять тесты, пора паузу взять :D
rotate+replace и delete-build самые быстрые по скорости, и они же не вызывают опасений в утечках памяти. Но второй потребляет память как будто равномернее.
Пожалуй, на этом пока всё.
Аватара пользователя
Jakob Brontfeyn

Activity Gold Silver Black
expert
expert
Сообщения: 1729
Зарегистрирован: 28 фев 2008, 11:01
Награды: 6
Благодарил (а): 1 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Jakob Brontfeyn »

Artem.spb писал(а): 13 дек 2021, 14:05
Jakob Brontfeyn писал(а): 13 дек 2021, 07:53 применяю метод с использованием Chathistory-массива
очень удобно, потому что, если надо, можно получить сразу несколько
синхронно ползучих массивов в одном чате.
вариант интересный, но скоростью совершенно не блещет. Во-первых, обращение к свойству элемента - очень медленная процедура, да ещё и на UI завязанная. А во-вторых, для получения фрагмента данных каждый раз придётся вытаскивать всю историю, чтобы потом кусок вырезать
Зачем же "кусок" вырезать, задайте длинну истории чата равной длинне "куска".
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Jakob Brontfeyn писал(а): 14 дек 2021, 23:49 Зачем же "кусок" вырезать, задайте длинну истории чата равной длинне "куска".
Задача изначально ставилась: хранить буфер "10 часов" с возможностью просматривать произвольный фрагмент (когда пользователь перематывает фрагмент). Конечно, можно сделать удобную навигацию по графику, тоже рабочий вариант.
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение dadreamer »

Artem.spb писал(а): 08 дек 2021, 21:49А последние два варианта мне не ведомы.
Я имел ввиду что-то такое:
789789567_BD.png
Что тут происходит: получаем указатель на "провод" массива, первым MoveBlock'ом записываем данные из этого массива (начиная со 2-го элемента) в него же (с "головы"), вторым MoveBlock'ом дописываем "хвост". Можно дополнительно отключить генерацию обёрток, чтобы не плодить лишние копии (но как показало дальнейшее тестирование, выигрыш от этого мизерный, т.к. копия массива итак не создаётся нигде, а копиями отдельных чисел можно пренебречь).

Дальше я встроил этот код в первый сниппет, получились такие результаты.
2021-12-28_17-51-44.jpg
И, если немного приблизить, будет лучше заметно.
2021-12-28_17-54-03.jpg
Время выполнения MoveBlock такое же как у delete-build (3) и rotate-replace (5). Думаю, и по смыслу примерно то же и получается: сдвиг - фактически, две записи, замена - одна запись. Отключение обработки ошибок и отключение обёрток на результаты практически не влияет.
Вот :vi: :
Ring Buf Test.vi
lv2017
(30.73 КБ) 44 скачивания
Artem.spb писал(а): 11 дек 2021, 14:55Не могу понять, почему индексированный while практически не уступает по скорости for-у.
Предположил, что в первом тесте цикл сразу знает количество итераций, повторил тест с имитацией неизвестности - результат тот же, скорость не существенно ниже, чем у For... :shok:
У кого-нибудь есть идеи объяснения этого?
Мне кажется, здесь виноваты два основных фактора. Во-первых, за пару десятилетий железо стало более продвинутым и отставание одной конструкции от другой почти не ощущается. Да, методички/бенчмарки уже устарели и по-хорошему их нужно подгонять уже под нынешние реалии. Во-вторых, в обоих случаях выполняется одно и то же, и там просто нечему тормозить (не считая операций с массивами; вероятно, в этом и была причина отставания While когда-то).
Копаться в ассемблере мне стало сильно лень, потому я просто сгенерил сишные портянки с помощью C Generator'а.
Для случая
2021-12-30_14-58-10.jpg
2021-12-30_14-58-10.jpg (12.77 КБ) 1974 просмотра
код такой:

Код: Выделить всё

		for (heap->l_For_Loop_i = 0;(heap->l_For_Loop_i < heap->l_Size) && !gAppStop && !gLastError; (heap->l_For_Loop_i)++) {
			{
				/**/
				/* Random Number (0-1) */
				/**/
				heap->n_Random_Number__0_1__number__0 = ((float32)SysRandom(0)/(float32)sysRandomMax);
				*(float64 *)LptunNthElem(heap->a_Random_Number__0_1__number__0) = heap->n_Random_Number__0_1__number__0; 
			}
		} /* end for */
Для случая
2021-12-30_15-00-08.jpg
2021-12-30_15-00-08.jpg (16.21 КБ) 1974 просмотра
код такой:

Код: Выделить всё

		heap->l_While_Loop_i_1 = 0;
		do {
			{
				/**/
				/* Random Number (0-1) */
				/**/
				heap->n_Random_Number__0_1__number__0 = ((float32)SysRandom(0)/(float32)sysRandomMax);
				heap->l_Size = Size__A8;
				/**/
				/* Greater Or Equal? */
				/**/
				heap->b_Greater_Or_Equal__x____y_ =  (heap->l_While_Loop_i_1 >= heap->l_Size);
				if (!PDAArrAddElToLpTunArr( (VoidHand)(&heap->a_Random_Number__0_1__number__0), (VoidHand)(&heap->n_Random_Number__0_1__number__0) )){
					CGenErr();
				}
			}
			(heap->l_While_Loop_i_1)++;
		}
		while(!heap->b_Greater_Or_Equal__x____y_ && !gAppStop && !gLastError);
Разница в двух вещах: в for условие выхода из цикла стоит вначале, в do-while - в конце; в for запись нового элемента в массив выполняется через функцию макрос LptunNthElem, в do-while - через функцию PDAArrAddElToLpTunArr. Да, в do-while ещё проверяется результат, возвращаемый функцией. На ассемблерном уровне никаких for и do-while, само собой, нет - только условные переходы, потому оба алгоритма будут "ближе" друг к другу.

upd: На самом деле есть ещё одно значительное отличие. Хотя в обоих случаях массив выделяется перед циклом с помощью функции PDAArrCreateLpTunArr, в случае For Loop массив выделяется с заранее определённым размером, но в случае While Loop массив выделяется пустым! Отсюда разное поведение при добавлении очередного элемента в массив в теле цикла: макрос LptunNthElem просто записывает элемент (число) в уже выделенную ячейку массива, тогда как функция PDAArrAddElToLpTunArr предварительно расширяет массив и только потом записывает элемент (число) в ячейку массива.
Последний раз редактировалось dadreamer 16 май 2023, 11:17, всего редактировалось 1 раз.
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

dadreamer писал(а): 30 дек 2021, 13:52
Artem.spb писал(а): 11 дек 2021, 14:55Не могу понять, почему индексированный while практически не уступает по скорости for-у.
Предположил, что в первом тесте цикл сразу знает количество итераций, повторил тест с имитацией неизвестности - результат тот же, скорость не существенно ниже, чем у For... :shok:
У кого-нибудь есть идеи объяснения этого?
Мне кажется, здесь виноваты два основных фактора. Во-первых, за пару десятилетий железо стало более продвинутым и отставание одной конструкции от другой почти не ощущается. Да, методички/бенчмарки уже устарели и по-хорошему их нужно подгонять уже под нынешние реалии. Во-вторых, в обоих случаях выполняется одно и то же, и там просто нечему тормозить (не считая операций с массивами; вероятно, в этом и была причина отставания While когда-то).
Именно что многие рекомендации базируются на устаревших взглядах на "мир".
Даже идея проверить MoveBlock. Помню в нулевых не получалось "запихать" математику в слабый PXI (просмотр массива по точкам и простейшие сравнения), пришлось мне на С dll сделать и эту самую математику нестандартно проводить. Сейчас бы я не стал таким заниматься, компиляторы :labview: сильно продвинулись с тех пор.

А ещё в таких тестах у меня всегда подозрения, делает ли машина то, что я прошу? Если провод из цикла никуда дальше не идёт, то компилятор может решить, что и заполнять этот массив не нужно.

По поводу заполнения в циклах на мой взгляд разница должна быть. Во втором случае На каждой итерации приходится выделять новый массив, да ещё и копировать данные туда


UPD: вот как в моём представлении (и в устаревших методичках) описывается автоиндекс в циклах While. И тут разница скоростей в 5 раз.
w-f.PNG
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение dadreamer »

Artem.spb писал(а): 30 дек 2021, 14:41А ещё в таких тестах у меня всегда подозрения, делает ли машина то, что я прошу? Если провод из цикла никуда дальше не идёт, то компилятор может решить, что и заполнять этот массив не нужно.
К сожалению, может, такова цена высокоуровневого программирования. Я на рамку просто завожу или на однокадровый Sequence. Наверно все, кто с :labview: знаком не понаслышке, использовали хоть раз такие трюки. :) Мне встречались всякие странности, например вызов DLL в SubVI выполнялся намного быстрее, чем в основном :vi: , или "чудеса" с Formula Node, когда она по-разному в разных условиях выполняется. Думаю, это из-за того, что не всегда генерируется одинаковое промежуточное представление (IR) и получаются разные графы, из которых LLVM уже производит машинные данные. Но это только гипотеза.
Artem.spb писал(а): 30 дек 2021, 14:41По поводу заполнения в циклах на мой взгляд разница должна быть. Во втором случае На каждой итерации приходится выделять новый массив, да ещё и копировать данные туда
Я это опустил, но в обоих случаях массив создаётся перед циклом:

Код: Выделить всё

		if (!(heap->a_Random_Number__0_1__number__0 = PDAArrCreateLpTunArr( (ArrDimSize)heap->b_While_Loop_End, doubleDataType, uCharDataType, (ArrDimSize)0 ))){
			CGenErr();
Первый параметр функции задаёт размер массива. В случае For Loop массив выделяется с заранее определённым размером, но в случае While Loop массив выделяется пустым. Отсюда разное поведение при добавлении очередного элемента в массив в теле цикла: макрос LptunNthElem просто записывает элемент (число) в уже выделенную ячейку массива, тогда как функция PDAArrAddElToLpTunArr предварительно расширяет массив и только потом записывает элемент (число) в ячейку массива. Так что да, разница есть. И общий вывод таков, что в этом сравнении While Loop работает медленнее, чем For Loop (даже если разница не всегда заметна).

После выхода из цикла выполняется преобразование из "туннельного" массива в 1D (что бы это ни значило):

Код: Выделить всё

		heap->a_Random_Number__0_1__number__0 = PDAArrCreate1DArrFromLpTunArr( heap->a_Random_Number__0_1__number__0 );
Последний раз редактировалось dadreamer 16 май 2023, 11:23, всего редактировалось 1 раз.
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

dadreamer писал(а): 30 дек 2021, 15:12 Я это опустил, но в обоих случаях массив создаётся перед циклом:
В случае с While в теории (и в общем случае) система не знает, сколько будет элементов. Например, я бегаю по файлу и собираю интересующие меня элементы.
Или, например, выборка из базы:
sql.PNG
В таких случаях не очень понятно, как компилятор строит код и как выделяет память под массив.
В предыдущем посте ("upd") я написал своё предположение на этот счёт. Но судя по скорости оно не верно.
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение dadreamer »

Artem.spb писал(а): 30 дек 2021, 15:20В таких случаях не очень понятно, как компилятор строит код и как выделяет память под массив.
Динамически, по одному элементу (ячейке) докидывает. Ну, как в Дельфях раньше создавали пустой динамический массив и постоянно его расширяли, чтобы впихнуть новые данные. Хотя, не знаю, чем отличается "туннельный" массив от обычного, может, как раз скоростью работы с ним.
Artem.spb писал(а): 30 дек 2021, 15:20В предыдущем посте ("upd") я написал своё предположение на этот счёт. Но судя по скорости оно не верно.
Надо листинг сделать, тогда понятнее будет. Посмотрю чуть позже.

Для случая с Build Array вместо туннеля:

Код: Выделить всё

		heap->a_Build_Array_appended_array_SR_1 = heap->a_Constant;
		heap->l_While_Loop_i_1 = 0;

		do {
			{
				heap->a_Constant_SR = heap->a_Build_Array_appended_array_SR_1;
				/**/
				/* Random Number (0-1) */
				/**/
				heap->n_Random_Number__0_1__number__0 = ((float32)SysRandom(0)/(float32)sysRandomMax);
/* Build array */
				{
					ArrDimSize i;
					ArrDimSize dimSize=0;
					heap->a_Build_Array_appended_array = PDAArrNewEmptyWithNDims( doubleDataType, (ArrDimSize)1 );
					if (!heap->a_Build_Array_appended_array){
						CGenErr();
					}
					dimSize += PDAArrNthDim(heap->a_Constant_SR, (ArrDimSize)0);
					dimSize += 1;
					PDAArrSetDim(heap->a_Build_Array_appended_array, (ArrDimSize)0, dimSize);
					if (!PDAArrAllocData(&heap->a_Build_Array_appended_array)){
						CGenErr();
					}
					i=0;
					if (!PDAArrAdd(heap->a_Build_Array_appended_array, i, heap->a_Constant_SR)) {
						CGenErr();
					}
					i += PDAArrNthDim(heap->a_Constant_SR, (ArrDimSize)0);
					PDAArrFree(heap->a_Constant_SR);
					if (!PDAArrSetData(heap->a_Build_Array_appended_array, i, &heap->n_Random_Number__0_1__number__0, doubleDataType)) {
						CGenErr();
					}
					i++;
				}
				heap->l_size = size__6E;
				/**/
				/* Greater Or Equal? */
				/**/
				heap->b_Greater_Or_Equal__x____y_ =  (heap->l_While_Loop_i_1 >= heap->l_size);
				heap->a_Build_Array_appended_array_SR_1 = heap->a_Build_Array_appended_array;
			}
			(heap->l_While_Loop_i_1)++;
		}
		while(!heap->b_Greater_Or_Equal__x____y_ && !gAppStop && !gLastError);
Намного больше операций, чем в предыдущем листинге. Используется промежуточный массив для пересылок данных из сдвигового регистра и обратно. Массив выделяется и освобождается на каждой итерации. Неудивительно, что этот подход получается медленнее. С туннельными :labview: , скорее всего, работает "на месте".

Короче, если всё на самом деле было так, как описано в старых методичках, получается, что компилятор действительно "допилили" до вменяемого состояния в новых версиях LV.
Artem.spb

Activity Автор
professor
professor
Сообщения: 3406
Зарегистрирован: 31 июл 2011, 23:05
Награды: 2
Версия LabVIEW: 12-18
Благодарил (а): 49 раз
Поблагодарили: 176 раз
Контактная информация:

Re: Ползущий массив (сравнение скоростей)

Сообщение Artem.spb »

Провёл оч странный тест с полной имитацией неизвестности. Конечно, накладные расходы гигантские, но сравнить производительность можно.
В целом For немного быстрее, но не существенно. А билдить массив руками всё же расточительно.
looptest.png
looptest2.png
Ответить
  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Для чайников»