15.09.2022

Trace Route

Короче, поломался тут у меня интернет. Ну, традиционно запустил ping, tracert: короче, шлюз "Сибирских сетей", к которому подключен мой роутер, иногда пропадает на несколько десятков минут, хотя физически соединение до коммутатора провайдера живое.

Пишу в тех. поддержку, понятное дело. И тут выясняю, что я отстал от жизни: ни ping, ни tracert нонче у сетевиков не котируются, им нужно специальную программку поставить и её логи выслать в тех. поддержку, что бы они могли проблему решить.
Программка PingPlotter называется. В принципе, хорошая программка, есть у нее только один недостаток: она платная. Хоть и стоит недорого, но все же, особенно в условиях санкций, имеет значение. Да и нужна-то она раз в пять лет, если не реже.
Ну и кстати, логи трассировки, полученные с помощью этой программки никак не помогли решить мою проблему: видимо, надо менять провайдера. Уже больше месяца "Сибирские сети" возятся, вроде на прошлой неделе ушла проблема, но на этой неделе снова вернулась. Хоть и не так критично, как раньше, но все же пару раз в день интернет на несколько минут пропадает.

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

В Delphi для подобных задач есть набор компонентов Indy, вроде десятой версии они у меня. Да вот незадача: компонентик для отправки ICMP запросов как-то странно работает. То есть просто пинг вроде норм, но как только ставишь маленький TTL, что бы сэмулировать трассировку маршрута, он вываливается со странной ошибкой: маленький размер буфера приема.
Проверил компонент Indy для трассировки, а не для пинга: та же шняга. Все же половина этих компонентов явно не доделана.

Порылся по интернету, нашел готовый модуль, который умеет отправлять ICMP-пинг безо всяких компонентов, используя лишь API Windows: Winsock и специальную библиотеку в довесок. Вполне себе рабочий, но вот только на Win64 он выдает ту же ошибку, что и Indy, а на Win32 норм.

Короче, фишка тут вот в чем. Windows, если в ответ вернулся ICMP-ответ с ошибкой, в буфер приема еще кладет дополнительно к обычной информации первые 8 байт из сообщения об ошибке принятого пакета. Поэтому если ICMP дошел до адресата без ошибки, то компонент Indy работает нормально, а вот если вернулась ошибка, например, "Время жизни (TTL) истекло в пути", то длины приемного буфера не хватает для помещения стандартного ответа ICMP плюс еще и сообщения об ошибки. К сожалению, видимо, разработчики компонентов Indy, видимо, не знали о такой особенности и дополнительно 8 байт в приемный буфер не выделили на случай возврата ошибочного пакета.
Но, удивительно, Win64 пишет в приемный буфер не 8, а 16 байт из сообщения об ошибке. Уж не знаю, почему. Поэтому найденный мною код и не работал в 64-битном режиме.
В общем, исходный файлик маленько подшаманил, добавил условную компиляцию, так что теперь он может работать и в 32-битном и в 64-битном режиме. Выложу здесь, может, кому пригодиться.
По хорошему, его немного надо доработать. Дело в том, что выполнения подобных ICMP задач в Windows добавлена специальная библиотека. Без нее, на голом WinSock, требуется, что бы приложение было запущено с привилегиями администратора, а с такой библиотекой можно пинговать и обычному пользователю.
Вот только на разных версиях Windows библиотека называется по разному: где-то Iphlpapi.dll, а где-то Icmp.dll. Так что по-хорошему, надо грузить библиотеки динамически, определяя, какой из вариантов на данной машине подходит.

Еще один любопытный момент связан с использованием указателей при вызове ICMP запросов. И для 32-битных, и для 64-битных систем используется одна и та же структура данных для вызовов функций, при этом все указатели, остаются, естественно 32-битными.
Как же это работает в 64-битной Windows, где указатели 64-битные? Оказывается, очень просто: 32-битный указатель расширяется (беззнаково) до 64-битного. Используется здесь тот факт, что Windows вроде всегда выделяет память под данные приложения с начала адресного пространства.
Вывод из этого безобразия: пока ваша программа вместе с данными влезает в 4 ГиБ, то все гарантированно будет работать, но как только станет больше - уже не факт ).

Ну, и кстати, в процессе узнал много интересного. Раньше думал, что, как и на Windows, на других операционках пользуются ICMP запросом Echo для пинга и трассировки маршрута. Но, оказывается, на Unix-подобных системах предпочитают отправлять UDP-запросы на неиспользуемый порт, ловя обратно ICMP-ответы по мере прохождения маршрута. Завершение маршрута определяет по ошибке о недоступности порта. В принципе, годный метод, но как быть, если порт на удаленной машине для чего-то используется? Ведь пингуя таким образом удаленную машину, мы не можем знать, какие порты на ней используются, а какие нет.

А еще бывают и более экзотические варианты пинга, на основе TCP. Но в эту сторону особо не смотрел.

01.09.2022

Оптимизация. Меньше насколько лучше?

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

Давайте посмотрим, что это даст. Предыдущий вариант показывал производительность 10.0 и 6.7 МиБ/с на AMD FX-4350.
Новый вариант ускорился, но все же не так сильно, как я ожидал: 10.7 и 7.4
МиБ/с соответственно.

Что будет, если вместо классических команд SIMD (в моем случае на технологии SSE) использовать в этом режиме команды SISD?
А вот что: 10.6 и 7.2 МиБ/с, то есть в общем-то особого смысла использования команд SIMD в самых простых случаях нет.

На уровне ассемблера вкратце это выглядит так: команды
  movdqu XMM0, [RCX+Limits+R11*2];
  paddw XMM0, XMM3;
  movdqu XMM0, [RCX+Limits+R11*2];

заменятся на
  add [RCX+Limits+R11*2], R8;
  add [RCX+Limits+R11*2+8], R8;

Здесь в регистрах XMM3 и R8 хранятся упакованные массивы из единиц длиной в слово: $1000100010001 для R8 и то же самое, 
только в два раза длиннее, для XMM3.

Проблемы переноса старших разрядов в младшие у соседних элементов не возникает, так как модель адаптируется, как только последний элемент с максимальным значением достигнет значения $FFFF.

При адаптации мы просто делим все элементы массива на два, после чего дополнительным проходом гарантируем монотонное увеличение всех элементов от первого до последнего.
Деление просто реализуется как на SIMD, так и на инструкциях общего пользования (GPI). Команда на ассемблере
  psrlw XMM0, 1;
заменятся на две пары из двух команд вида
  shr R9, 1;
  and R9, R8;

Вторая команда AND нужна для того, что бы очистить случайно залетевшие младшие биты элементов с большими индексами в старшие биты с элементами с меньшими индексами. Значение для R8 такое: $7FFF
7FFF7FFF7FFF.
Спросите меня, зачем я в командах общего пользования использую регистры с константами вместо указания констант в самих командах? Ответ очень прост: в X64 нет возможности указывать константы в командах длиной 64 бита.

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

Ладно, хорошо, но мой FX-4350 уже достаточно старенький процессор. Возможно, SSE  на нем реализовано не так уж оптимально?
Сравним с Ryzen 7 5800X: SIMD - 19.56 и 12.61
МиБ/с, GPI - 19.63 и 12.46 МиБ/с. Как видим, разницы никакой, следовательно, использовать SIMD оправдано только в случае несколько более сложных вычислений, которые никак не укладываются в инструкции общего назначения.

Ну и кстати, вариант с уменьшенным количеством делений и короткой таблицей чуть-чуть обошел вариант с пирамидой (17.8 и 13.3 МиБ/с). Есть подозрение, что если реализовать вариант с пирамидой с уменьшенным количеством делений, то удастся выйти на современных процессорах за 20 МиБ/с при сжатии данных.
А вот при распаковке, видимо нет, даже при переходе на 16-битный алфавит.