Pull to refresh
137
-3.7
Андрей Дмитриев @AndreyDmitriev

Пользователь

Send message

Спасибо! Если разрядность данных учитывать, то, конечно, асимптотика будет ещё хуже — там ведь не только больше проходов, но и больше сравнений потребуется. Но я всё-таки за сложность от количества данных, а не от типа. Практически же это действительно не так часто бывает нужно, да и отсутствие функции вычисления медианы в трёх библиотеках обработки изображений как бы намекает. Но если у кого-то медиана - "бутылочное горлышко", то как вариант.

Требуемая точность в общем от задачи зависит; если отрезать младший байт от float, то по моим прикидкам мы потеряем что-то около сотой процента в точности. Можно даже два байта отчекрыжить, тогда потеряем всего-то около процента. Но если оставить только один байт, то потеря будет значительна - ведь у нас в распоряжении будет всего 256 значений на весь диапазон. В данном же случае интерес был более "спортивный", нежели прикладной — получить ровно тот же результат, что выдают пара других библиотечных функций, но за меньшее время.

Про 8 бит я думал, но необходимость увеличенного вдвое количества проходов прибьёт идею, особенно если исходный массив в кэш не влезет — память, она медленная очень, кроме того чем дальше спускаться к младшим байтам, тем больше сравнений. Simd заюзать можно, но там очень много времени положить придётся. Простое включение AVX2 ничего не даёт - это я проверил, впрочем ассемблеровский листинг не смотрел. Проще вначале взять интеловский компилятор и посмотреть, что там векторизатор скажет.

Это так, просто по моим наблюдениям классический quickselect в среднем примерно этак вдвое-втрое медленнее гистограммного метода. Собственно в nimpy вариации на тему quickselect и реализованы в selection.cpp И там и там линейное время, но общий наклон линии тоже хотелось бы уменьшить. Это больше не про теоретическую сложность, а про практическое время выполнения на данном наборе данных.

Абсолютно серьёзно. Есть медианная фильтрация, а вот просто значения медианы по каналу — нет. Все изобретают велосипед либо через cv::calcHist (), либо как я, ну или средним вместо медианы обходятся.Я по исходникам 4.8.1 поискал как Median так и NthOrder, но правда не нашёл.

Да как бы ничто не мешает, но сложности (n*log(n)) именно и хочется избежать. Исходные данные изначально несортированны, разумеется. А так сложность остаётся линейной, нам хочется детерминированного O(n). Ну и любой алгоритм поиска медианы, основанный на сортировке просядет по производительности на больших массивах. Это вот здесь уже обсуждалось немножко - Мой любимый алгоритм: нахождение медианы за линейное время

Пункт 3 надо заменить на "и наличии железки, не поддерживающей терминирующий символ".

Вообще учебник пишет, что "Keep in mind that termination on the physical hardware needs to comply with the appropriate Serial standard. If the termination on the physical component is incorrect then it will cause issues with termination characters on the software side".

Так что там не всё так просто. У меня не было проблем с терминирующим байтом, но там это было заложено в самих железках (сканер штрих кодов обычно так работает, но там никогда не будет этого байта в нормальных данных, поскольку там просто ASCII текст штрих-кода прилетает, а не двоичные данные).

Ну и опять же "When performing binary communication, the read can terminate prematurely if one binary data value has the same binary representation as the termination character. Therefore, disable the termination character by setting Termination Character Enabled to false and Serial End Modes for Reads to None (0), as shown below. You must rely on a different method of terminating the read, such as a hardware line or byte count".

Тот факт, что вы таки получаете терминируюий символ в данных и размер пакета увеличивается с 6 до 7, дополняясь двумя нулями - любопытен (я бы ожидал, что LabVIEW проглотит байт 10 и не отдаст его наружу).

Но это утверждение легко проверить - собирается тестовый стенд, пересылаются байты в последовательный порт и смотрится, что там получается и так и сяк, заодно с ASRL End In/Out поиграть.

Да не, я много всякой "мистики" видел, но тут явно дело не в периодичности сигнала.

LabVIEW добавляет “заголовок” (свои байты) если последовательность данных носит периодический характер

Я вот смотрю - проблема возникает, когда вам прилетает байт 10. По умолчанию это терминирующий байт и он включён. Попробуйте выключить этот режим при открытии порта:

Я не уверен, что это причина такого поведения, но я абсолютно уверен, что если я возьму ардуинку и начну генерить синус в COM порт, и принимать его в LabVIEW, то ничего лишнего там вставляться не будет.

Я обычно говорю в таких случаях, что любому наблюдаемому феномену есть рациональное объяснение и чудес на свете не бывает. Сколько я с RS232 не работал, вот ни разу не встречал ситуации, когда просто так появлялись бы "мусорные" байты из ниоткуда. Думаю, что их железка шлёт, ну или помехи на линии. Хардкорный метод - подцепиться на Tx Rx осциллографом с памятью прямо у порта, и записать и проанализировать все сигналы, которые пролетают через интерфейс.

Вы несколько нетипично используете комбинацию Wait (ms) вкупе с Wait Until Next ms Multiple c довольно близкими значениями задержек (8 и 10 мс). Это может приводить к тому, что время между чтением и записью будет "плавать", возможно железка ещё не готова принять байт - команду на чтение и реагирует на это дополнительным "мусорным" байтом. Возможно "двухцикловый" дизайн с очередью чуть меняет тайминги и эффект пропадает. Теоретически можно Timed Loop попробовать, хотя там можно налететь на другие "подводные камни".

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

Понятно, спасибо. Теоретически, пользуясь конечными автоматами можно создать любую схему, это так.

Кстати, мне несколько лет назад понадобился конечный автомат на чистом Си, и я, помнится, позаимствовал пару идей вот здесь:

State Machine Design in C

ну или если на плюсплюсах:

State Machine Design in C++

Код получается гибким и расширяемым, его логику легко понять, даже в случае сложного автомата.

Если по какой-либо причине требуется задержка на один такт, то это элементарная модификация, что в Си, что в LabVIEW. Вообще правильный результат можно где угодно получить, это больше вопрос усилий, затраченных для его получения, ну и постановки задачи, естественно.

У меня возникает затык со временем, так что я больше не буду так активен, но хочу поблагодарить за приятное и конструктивное общение, спасибо!

Не, этот элемент (Build Array) к триггеру не имеет отношения, он просто собирает пять битов (два входных, два выходных и ИЛИ-НЕ) в массив, который потом конвертируется для отображения на цифровом графике. Если его убрать, то триггер работать не перестанет, просто мы графика лишимся.

Длительность импульса - просто один такт. Тут крутится for цикл, никакого другого тактового генератора здесь нет. Мне проще на Си показать, вот практически полностью эквивалентный код (длины массивов я захардкодил, мы ж не в продакшене):

#include <ansi_c.h>
#include <stdbool.h>

BOOL RS_Trigg (BOOL S, BOOL R, BOOL *Q, BOOL *_Q)
{
	static BOOL q = false;
	static BOOL _q = true;
	BOOL s = !(q || S);
	BOOL r = !(_q || R);
	*_Q = _q = s;
	*Q = q = r;
	return !(q || _q);
}

void main (int argc, char *argv[])
{
	int s[12] = {0,1,0,0,0,1,0,0,0,0,0,0};
	int r[12] = {0,0,0,1,0,1,0,0,0,0,1,0};
	BOOL S[72], R[72], Q[72], OrNot[72], notQ[72];
	
	for (int i = 0; i < 12; i++){
		for(int j = 0; j < 6; j++){
			S[i * 6 + j] = (BOOL)s[i];
			R[i * 6 + j] = (BOOL)r[i];
		}
	}
		
	for (int i = 0;  i < 72; i++) OrNot[i] = RS_Trigg(S[i], R[i], &Q[i], &notQ[i]);
	
	printf("\n     S:"); for (int i = 0;  i < 72; i++) printf("%d", S[i]);
	printf("\n     R:"); for (int i = 0;  i < 72; i++) printf("%d", R[i]);
	printf("\n     Q:"); for (int i = 0;  i < 72; i++) printf("%d", Q[i]);
	printf("\n  NotQ:"); for (int i = 0;  i < 72; i++) printf("%d", notQ[i]);
	printf("\n OrNot:"); for (int i = 0;  i < 72; i++) printf("%d", OrNot[i]);
}

Ну а результат:

     S:000000111111000000000000000000111111000000000000000000000000000000000000
     R:000000000000000000111111000000111111000000000000000000000000111111000000
     Q:000000011111111111000000000000000000101010101010101010101010000000000000
  NotQ:111111000000000000011111111111000000101010101010101010101010111111111111
 OrNot:000000100000000000100000000000111111010101010101010101010101000000000000

Это ровно то, что на графике выше изображено.

Вообще LabVIEW нынче бесплатна для некоммерческого использования в виде Community Edition, а по всем вопросам можно на форум LabVIEW Portal обращаться - там очень доброжелательные ребята и всем охотно помогают, я тоже там бываю.

Ну я конечно могу снова переключиться на feedback node, задвинуть их под ИЛИ-НЕ примитивы, а сверху набросить оригинальные иконки, не поленюсь даже натыкать диагональные проводочки:

Уж и не знаю — куда аутентичнее. Но я очень редко так делаю, поскольку, если скажем включить подсветку исполнения кода, то вот так сходу будет неясно, откуда возникают осцилляции:

Не говоря уже о том, что код этот способен вынести мозг любому LabVIEW программисту.

Куда понятнее вот так:

Что касается элемента ИЛИ-НЕ, подключённого к выходам триггера — то тут я совсем не понял — этот элемент будет честно отрабатывать логику в соответствии с таблицей истинности:

Для наглядности я возьму триггер, показывающий проход через запрещённое состояние - в этот момент на графике будут короткие всплески:

Что касается параллельности, то триггер этот — потокобезопасный (просто за счёт того, что реентерантный), я могу вызывать его из многих асинхронных потоков и он будет корректно работать безо всяких дополнительных семафоров, притом абсолютно параллельно, не блокируясь.

в суде у неё нет шансов опровергнуть китайское происхождение своей продукции

У Фемиды временами бывает весьма скверный характер, знаете ли. Найдите хорошего адвоката на всякий случай.

Мягкие стены, возможно полёт мысли и не остановят, а вот хорошая, качественная доза галоперидола или аминазина - очень может быть. И даже рискну предположить, что некоторым черезчур активным пациентам она бы отнюдь не помешала.

С другой стороны, к примеру Франц Кафка, по общему мнению страдал определёнными расстройствами, но если и да, то возможно именно они "помогли" ему описать мир абсурда (иногда кажется, что мы в таком, кафкианском мире и живём - казалось бы, всё уже и так плохо, ан нет, мы сделаем ещё хуже).

По поводу сотворения мира и всего такого могу ещё посоветовать полистать книжку - Генрих Корнелий Агриппа - Оккультная философия, там по мнению автора всё состоит из четырёх основных элементов, а остальное - трансмутации (я просто прям сейчас начал читать, до конца ещё далеко, но отдельные места изложения и взглядов древних всегда любопытны).

А, теперь понял, ну конечно – я ж кручу цикл дважды, чтобы передать сигналы с выходов на входы, а эти осцилляции, они на каждый такт возникают, и я их благополучно подавляю. Я уберу логику для обработки "запрещённого" состояния (всё равно выходы свалятся в низкий уровень), и верну второй сдвиговый регистр, но сделаю только один проход:

И вот, теперь я вижу проход через запрещённое состояние (я ещё сдвину R и S на графике на полтакта влево для наглядности), а также вход в режим генерации и выход из него:

А если я буду делать три итерации (могу ещё сброс для первого старта добавить):

то фронты выходов сядут друг на друга, а всё остальное останется:

Вот теперь паззл сложился.

Код, которым генерировался график

Мог бы и сразу догадаться так сделать. Старею, наверное.

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

Что касается диаграмм, то прежде чем я расчехлю генератор и осциллограф, я слегка модифицирую код. Дело в том, что мне не нужно два сдвиговых регистра, чтобы хранить один бит информации (я это сделал выше просто для наглядности), поэтому в реальной жизни будет как-то так (обычно и инвертированный Q не нужен, но пусть будет):

Второе изменение, которое я сделаю - это явная обработка "запрещённого" состояния, когда оба входа активны. Здесь я добавлю выход ошибки, а сигналы с обоих выходов буду просто убирать на время ошибки (они сами восстанавливятся обратно после возврата в нормальное состояние):

Вот теперь осциллограмма:

При первом "включении" сигнал Q неактивен, а инвертированный - установлен, это валидное состояние, почему бы и нет. Затем я устанавливаю вход S, - устанавливается Q, защёлкивается и держится и после снятия S. R снимает его обратно. При установке обоих R и S в активное состояние взводится выход ошибки и сигналы Q и ^Q снимаются (так обычно делают, хотя я могу их и оставить если надо). Что касается момента перехода из R=S=1 в R=S=0, то ни о какой метастабильности тут речи не идёт - триггер просто восстанавливает своё предыдущее состояние.

Что касается машины Тьюринга и параллелизма, то я считаю, что параллелизм не отменяет полноту по Тьюрингу, но это больше вопрос философский.

"А не замахнуться нам на Вильяма нашего Шекспира", т.е. на создание RS-триггера на LabVIEW? Пройдет ли сейчас LabVIEW уже нынче такой тест на параллелизм?

Сакральный смысл "RS-триггер теста" от меня честно говоря несколько ускользает, но тем не менее, классически "защёлки" реализуются в LabVIEW при помощи неинициализированных сдвиговых регистров. Вообще говоря модель потоков данных не позволяет мне завести выходы обратно на входы, но в простейшем случае это ограничение обходится вот так:

Если заменить сдвиговые регистры фидбек нодами, то всё будет совсем красиво, почти как в учебнике:

Но! Это абсолютно детерминированный код, он никогда не вывалится в метастабильное состояние (а насколько я понимаю, это и является критерием "правильности" реализации триггера, или нет?), поскольку здесь происходит неявная синхронизация, вызванная двумя проворотами цикла. Чтобы избавиться от навязанного детерминизма мне нужно ввести небольшие искусственные задержки обратного распространения сигналов. Самое простое решение - как-то вот так, что ли, одной спонтанной миллисекунды в таймауте мне вполне хватит:

Я тут стрелочками показал, как происходит перехлёст сигналов. Теоретически можно и одним циклом обойтись, просто его крутить надо больше и скорее всего будет больше обвеса и меньше элегантности. Вот теперь по крайней мере при переходе обоих входов в низкое состояние после высокого уровня мы будем спонтанно получать сигнал на одном из выходов, как-то так. Но по большому счёту если мы возьмём полный по Тьюрингу язык (каковым и LabVIEW является), то мы ведь можем реализовать вообще любую логику поведения триггера, включая симуляцию метастабильности (хотя с ней обычно стараются таки бороться), в зависимости от внешнего воздействия и предыдущего состояния как в однопоточном, так и в многопоточном окружении, разве нет?

Если вставить структуру последовательности, вот так

то снова будет 1000. Эта структура служит "синхронизирующим барьером", выполняя роль рандеву - слева от неё чтение переменных может произойти в разные моменты, но исполнение продолжится только когда придут данные от обоих инкрементов, они будут одинаковы, которые записываются обратно в переменную, при этом новая итерация цикла начнётся только после записи, поэтому всегда будет ровно 1000. Synchronize Data Flow на скриншоте в предыдущем комменте делает ровно тоже самое, просто более компактно выглядит.

У меня в далёких планах на досуге посмотреть Active Oberon применительно к параллельности (я в прошлой жизни на Модуле-2 немного программировал) и вот ещё Раст интересен - там по идее потокобезопасность ещё на этапе компиляции должна отслеживаться, но я пока совсем новичок в этой области.

Там много факторов может быть, я вот в прошлом месяце упражнялся с реализацией Flat Field Correction, используя AVX-2/AVX-512, и там я упёрся в скорость чтения из памяти и записи, хоть я и мимо кэша писал, и по итогу AVX-512 показал такую же или чуть худшую производительность, так как судя по всему "бутылочным горлышком" была именно память, а команды AVX-512 к тому же чуть просаживали частоту и в результате выигрыш нивелировался.

Information

Rating
Does not participate
Location
Ahrensburg, Schleswig-Holstein, Германия
Date of birth
Registered
Activity