TheLostJedi Ответов: 1

Как ускорить время возврата файла ReadFile?


Я разрабатываю приложение, которое считывает фиксированный пакет данных из com-порта с регулярными интервалами в 50 мс. Я использую CreateFile и ReadFile чтобы открыть и прочитать из порта соответственно. Существует небольшое количество обработки, которая происходит с данными, которые считываются.

Поскольку выбор времени жизненно важен в таком приложении, я решил проверить, сколько времени занимает каждая из отдельных задач. То ReadFile функция вызывалась каждые 50 мс или всякий раз, когда EV_RXCHAR событие произошло. Обработка занимает едва ли миллисекунду. Это все хорошо. Но ... ReadFile функция занимает в среднем 15 мс, чтобы вернуться. Кажется, что это ужасно много времени для чтения данных из порта. Я отправляю данные в порт через стороннее приложение, которое записывает данные в виде пакетов, так что это не может быть (я думаю) из-за его скорости записи.

Это становится проблемой из-за следующего: я сделал вышеуказанные тесты с виртуальными com-портами. Когда я тестирую его с помощью некоторого физического оборудования, затраченное время может варьироваться от 47 мс до 127 МС. Аппаратное обеспечение - это карта, предназначенная для упаковки данных и отправки их в виде пакетов. Такая задержка приводит к тому, что мое приложение довольно быстро выходит из синхронизации.

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

PS: я делаю неперекрывающееся чтение и запись специально.

Что я уже пробовал:

Я не знаю, что здесь добавить, поэтому добавляю свой код функции чтения. Он находится в бесконечном цикле, пока не проинструктирован иначе.

void CGCUGUIDlg::fnRead()
{
	
	while(m_bCount==true)
	{
		
		{
			
			WaitCommEvent(m_hComm,&dwEventMask,NULL);
			if(dwEventMask==EV_RXCHAR)
			{
				DWORD NoBytesRead;
				BYTE  abBuffer[100];
				if(ReadFile((m_hComm),&abBuffer,sizeof(abBuffer),&NoBytesRead,0)) //This takes over 47ms with physical hardware
				{
					midTime1 = GetTickCount();
					if(NoBytesRead==45)
					{
						
if(abBuffer[0]==0x10&&abBuffer[1]==0x10||abBuffer[0]==0x80&&abBuffer[1]==0x80)
						{
							this->fnSetData(abBuffer);
						}
						else
						{
							CString value;							
							value.Append("Header match failed");
							this->SetDlgItemText(IDC_RXRAW,value);	
						}
					}
					else if (NoBytesRead<45 && NoBytesRead>0)
					{
						CString value;
						value.Append(LPCTSTR(abBuffer),NoBytesRead);
						value.Append("\r\nInvalid Packet Size");
						this->SetDlgItemText(IDC_RXRAW,value);
					}
					
				}
				else
				{
					DWORD dwError2 = GetLastError();
					CString error2;
					error2.Format(_T("%d"),dwError2);
					this->SetDlgItemText(IDC_RXRAW,error2);
				}
				
			}
		}
		
	}
	CloseHandle(m_hComm);
		
	
}


PS: Я приношу свои извинения за ужасное форматирование и случайные скобки.

PIEBALDconsult

Это не очень поможет, но я думаю, что вы должны создать экземпляр массива-BYTE abBuffer[100] - один раз, перед входом в цикл, а не создавать его на каждом цикле цикла.
Кроме того, то, что я делал в прошлом, заключается в том, чтобы чтение и обработка выполнялись в отдельных потоках.
То есть разделите заботы.
Я всегда слышу мантру моего школьного футбольного тренера: "получи это, избавься от этого; получи это, избавься от этого..."

TheLostJedi

Thank you for that reply!! The reason I create a new instance of the buffer each time is to ensure I have an empty buffer every time, and also it's kind of neat that the previous buffer gets cleaned up automatically once the scope ends. Also, this read is happening in a separate worker thread and because processing can happen only after the read is done I didn't think too much about allocating separate resources for the processing. But the main issue is that I can time and see that the ReadFile is taking a massive amount of time. Just that one line. The total time of each loop is actually the ReadFile time plus 1 or 2ms. I realise that these are inbuilt API calls, but I was hoping there was a way to speed it up.

Kornfeld Eliyahu Peter

Если синхронизация важна, вы не можете установить фиксированный временной интервал, а только использовать события для связи...
Com - порты построены на блоке UART, который может работать с различной скоростью (baudrate), чтобы соответствовать подключенному устройству...
Вы не показали, как вы открыли порт, но вы можете попробовать другую настройку скорости передачи данных...

TheLostJedi

Спасибо Вам за ответ!! Я использую чтение на основе событий именно по той причине, о которой Вы упомянули; синхронизация почти невозможна при синхронизированном чтении не в последнюю очередь из-за неточных таймеров.
Я прошу прощения за то, что не показал, как был открыт порт. Он был установлен на скорость передачи данных 115200 бит / с, и, к сожалению, это не может быть изменено и является тем же самым для устройства, отправляющего данные. Но я предполагаю, что это поставило бы конечный нижний предел времени, которое занимает каждое чтение, верно?

Kornfeld Eliyahu Peter

Вы написали: "считывает фиксированный пакет данных из com-порта с регулярными интервалами в 50 мс"... Это не похоже даже на базис :-)
115200 - это вообще хорошая скорость. Передача 100 байт на этой скорости должна занять около 10 мс, так что это звучит хорошо...
Две вещи:
1. Почему вы закрываете com-порт в конце функции?
2. Есть ли у вас инструмент мониторинга портов, чтобы увидеть, что на самом деле идет и приходит?

TheLostJedi

Данные передаются с такой скоростью, я не могу это контролировать. Я читаю в событийной манере, проверяя наличие события EV_RXCHAR.
1. Это был мой способ прекратить чтение и выйти из потока. Флаг (m_bCount) будет превращен в false из моего основного потока, чтобы сигнализировать этому потоку чтения, чтобы остановить все.
2. Я проверил, что. Все передаваемые данные хороши.
Моя проблема все еще заключается в том, что после всего этого функция ReadFile занимает много времени, чтобы вернуться, что на самом деле не имеет для меня никакого смысла.

1 Ответов

Рейтинг:
6

Jochen Arndt

Ты звонишь ReadFile() с nNumberOfBytesToRead = sizeof(abBuffer) Таким образом, функция вернется после того, как будет получено 100 байт, что заняло некоторое время.

Вместо этого вы можете получить количество доступных байтов и передать их (когда EV_RXCHAR был подан сигнал):

DWORD dwComErrors;
COMSTAT tComStat;
if (::ClearCommError(m_hComm, &dwComErrors, &tComStat))
{
    // Call ReadFile() with nNumberOfBytesToRead = tComStat.cbInQue
}

Для дальнейшей оптимизации вы также должны выполнять чтение в рабочем потоке с использованием перекрывающегося ввода-вывода (с двумя OV-структурами для WaitCommEvent и ReadFile) вызов WaitForMultipleObjects чтобы дождаться событий связи и завершения потока. Тогда чтение из последовательного порта не будет блокировать ваше приложение. Однако тогда вы не можете устанавливать объекты GUI напрямую, а должны реализовать какой-то вид передачи данных (например, путем публикации пользовательских сообщений).

Я всегда буду использовать рабочий поток. Тогда графическая часть вашего приложения не блокируется даже тогда, когда ReadFile блоки из-за ожидания большего количества данных, чем на самом деле доступно. Это также позволит избежать блокировки вашего приложения, если устройство отключено.

Некоторые полезные ссылки (довольно старые, но все еще действующие:
последовательная связь[^]
Последовательная связь в Windows[^]
Последовательный порт ввода-вывода[^]


TheLostJedi

Спасибо Вам за ответ!! Я действительно попытался заменить 100 байт точно таким же размером отправляемого пакета данных (фиксированным и постоянным в 45 байт), но время, необходимое для возврата функции readfile, осталось прежним.
Кроме того, я делаю неперекрывающееся чтение, потому что мне легко организовать команды чтения и записи. И то, что вы сказали о рабочем потоке, верно, и именно поэтому я поместил вызов read в поток.
Я начинаю думать, что, возможно, задержка просто присуща, но тот факт, что эта задержка не происходит при каждом чтении, заставляет меня сомневаться в этом.

Jochen Arndt

При фиксированном размере пакета 45 и выполнении чтения в рабочем потоке может не потребоваться вызывать WaitCommEvent. Просто попробуйте прочитать 45 байт. Чтобы избежать проблем с синхронизацией, увеличьте размер буфера чтения, вызвав SetupComm (), и при необходимости увеличьте приоритет потока до выше нормального (выше приоритета обычных приложений). Это должно предотвратить рассинхронизацию (по крайней мере, если нет очень высокой нагрузки на систему от низкоуровневого ввода-вывода, такого как диск и сеть).

Ваше приложение может аварийно завершить работу при вызове SetDlgItemText() из потоков, которые не владеют элементом управления. Возможно, вы не сталкивались с этим до сих пор, потому что вы используете это только при ошибках.