HISTORY
The gzip program was originally written by Jean-loup Gailly, licensed under the GNU Public Licence.
Matthew R. Green wrote a simple front end for NetBSD 1.3 distribution media, based on the freely re-distributable redistributable
distributable zlib library. It was enhanced to be mostly feature-compatible with the original GNU gzip
program for NetBSD 2.0.
This implementation of gzip was ported based on the NetBSD gzip, and first appeared in FreeBSD 7.0.
~ uname
Darwin
~ gzip --version
Apple gzip 264.50.1
lorca@defaultvps:$ uname
Linux
lorca@defaultvps:$ gzip --version
gzip 1.6
Copyright (C) 2007, 2010, 2011 Free Software Foundation, Inc.
Copyright (C) 1993 Jean-loup Gailly.
This is free software. You may redistribute copies of it under the terms of
the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.
Written by Jean-loup Gailly.
Вот способ по скорости сопоставимый с версией использующей таблицу на 1Mb но без таблицы. Не очень простой для понимания, но быстрый и без дополнительной памяти
Наконец руки дошли проверить на старом RaspberryPi c armv6(ARM1176JZ-S). Он не суперскалярный, single-issue и поэтому более короткий цикл там работает быстрее.
despace(buffer, N) : 9.34 ns per operation
despace_ptr(buffer, N) : 8.03 ns per operation
Примечательно так же то, что gcc 7.1.1 не сгенерировал оптимальный код, только clang 4. Gcc почему-то постеснялся использовать
В первом варианте запись и сравнение не зависят друг от друга, а зависят только от ldrb. При этом strb из того же регистра, в который идет ldrb может начаться на такт раньше, чем другие инструкции, зависящие от этого регистра.
Во втором случае запись зависит от сравнения, и не может начинаться раньше, чем выполнилось сравнение, которое в свою очередь дожидается результата ldrb. Вот и вся разница, счетчик либо указатель тут не причем.
Оптимизированный вариант neon_despace_branchless не зависит от процента пробелов.
Финальная расстановка точек. Тест на Cortex A7. Результаты по скорости:
despace(buffer, N) : 4.21 ns per operation
despace_ptr(buffer, N) : 5.25 ns per operation
neon_despace(buffer, N) : 3.33 ns per operation
neon_despace_branchless(buffer, N) : 3.69 ns per operation
Где dspace это:
size_t i = 0, pos = 0;
while (i < howmany) {
const char c = bytes[i++];
bytes[pos] = c;
pos += (c > 32 ? 1 : 0);
}
return pos;
dspace_ptr:
char *i = bytes, *pos = bytes;
const char *end = bytes + howmany;
while (i < end) {
register char c = *i++;
if (c>' ') { *pos++ = c;}
}
return pos - bytes;
Как видно из результатов код с меньшим количеством инструкций выполняется медленнее. Подробное объяснение потянет на отдельную статью, но если кратко, то важно не только количество инструкций, но и зависимости между ними, и это то, что компилятор умеет выстраивать достаточно не плохо, если ему не мешать. Например на Cortex A7 пара ldr/str для одного и того же регистра выполняется столько же, сколько простой ldr.
Также
addhi r0, r0, #0x1
subs r1, r1, #0x1
выполнятся за 1 такт потому что поддерживается dual issue для инструкций читающих по одному регистру.
Вот так, примерно, будет выглядеть выполнение кода по тактам: 1 ldrb r2, [r3]!, #0x1
2 strb r2, [ip, r0]
3 cmp r2, #0x20
4 addhi r0, r0, #0x1
4 subs r1, r1, #0x1
5 bne loc_10554
Мне кажется что на Cortex A15 не будет разницы по скорости выполнения межу strhib r2, [ip], #1 и strhib r2, [ip]; addhi ip, ip, #1. А на Cortex A7 скорее всего будет, это я чуть позже проверю.
В ARM64 больше нету условного выполнения каждой инструкции, есть b, и csel.
На обычном ARM индексирование тоже не стоит времени. LDR (register offset). Последний вариант чуть быстрее, но все еще хуже способа из статьи.
despace(buffer, N): 0.79 ns per operation
despace3(buffer, N): 1.19 ns per operation
if вместо тернарного оператора генерирует код через csel + move вместо csinc, что делает цикл на одну инструкцию больше (7 vs 6). Вообще заменять работу с массивами и индексами на указатели — только мешать компилятору оптимизировать.
Поскольку в статье речь идет об arm64 то и скорость я сравнивал на arm64.
Вот результаты:
despace(buffer, N): 0.79 ns per operation
despace2(buffer, N): 1.40 ns per operation
1) Превращение индекса в указатель занимает 0 инструкции (ldrsb w10, [x9], #0x1)
2) Безусловная запись быстрее чем условная, т.к. нет лишнего условного перехода.
3) Проверка генерирует дополнительный условный переход. в то время как тернарный оператор дает одну безусловную инструкцию cinc x0, x0, gt.
В итоге во втором варианте больше ветвлений, которые портят конвейер.
Да, как раз график SNR и убрали, остальное есть. В debug информации тоже не нашел.
Упс, это я наврал с трейсроутом, впн был через Австрию подключен. Вот правильный
traceroute to 159.223.130.136 (159.223.130.136), 64 hops max, 52 byte packets
1 starlinkrouter (192.168.1.1) 80.506 ms 2.901 ms 3.156 ms
2 136.22.97.33 (136.22.97.33) 163.111 ms 112.612 ms 35.243 ms
3 136.22.254.44 (136.22.254.44) 143.633 ms 174.254 ms 157.615 ms
4 ae46-0-xcr1.fix.cw.net (195.89.98.65) 176.609 ms 39.401 ms 289.743 ms
5 ae17.pcr1.fnt.cw.net (195.2.20.226) 40.037 ms
if-ae-55-2.tcore2.pvu-paris.as6453.net (80.231.245.6) 143.583 ms 157.497 ms
6 if-ae-49-2.tcore1.pvu-paris.as6453.net (80.231.153.20) 354.200 ms 303.408 ms 178.307 ms
7 if-ae-55-2.tcore1.pye-paris.as6453.net (80.231.154.30) 178.084 ms
if-ae-11-2.tcore1.pye-paris.as6453.net (80.231.153.50) 126.836 ms 163.887 ms
8 prs-bb2-link.ip.twelve99.net (62.115.114.98) 177.876 ms *
prs-bb1-link.ip.twelve99.net (62.115.123.13) 131.409 ms
9 ldn-bb4-link.ip.twelve99.net (62.115.133.238) 55.420 ms
ldn-bb4-link.ip.twelve99.net (62.115.114.228) 56.924 ms
ldn-bb1-link.ip.twelve99.net (62.115.135.24) 63.724 ms
10 nyk-bb2-link.ip.twelve99.net (62.115.113.20) 127.517 ms
nyk-bb1-link.ip.twelve99.net (62.115.112.244) 126.303 ms
if-be-2-2.ecore1.n75-newyork.as6453.net (66.110.96.62) 126.239 ms
11 if-ae-57-2.tcore1.n75-newyork.as6453.net (66.110.96.58) 125.457 ms
if-ae-14-2.tcore3.nto-newyork.as6453.net (63.243.186.16) 127.999 ms
if-ae-57-2.tcore1.n75-newyork.as6453.net (66.110.96.58) 134.261 ms
12 if-be-2-2.ecore1.n75-newyork.as6453.net (66.110.96.62) 126.626 ms
66.110.96.26 (66.110.96.26) 127.960 ms 125.873 ms
13 138.197.244.6 (138.197.244.6) 130.627 ms * *
14 * * *
15 * * *
16 * * *
17 * 159.223.130.136 (159.223.130.136) 128.828 ms 125.300 ms
traceroute
traceroute to 159.223.130.136 (159.223.130.136), 64 hops max, 52 byte packets
1 10.1.0.1 (10.1.0.1) 69.259 ms 69.228 ms 55.661 ms
2 94.198.41.209 (94.198.41.209) 74.706 ms 59.823 ms 63.216 ms
3 37.120.220.142 (37.120.220.142) 74.306 ms 84.343 ms 152.816 ms
4 vlan2910.bb2.vie1.at.m247.com (82.102.29.30) 65.869 ms 107.932 ms 65.613 ms
5 win-b2-link.ip.twelve99.net (62.115.146.18) 80.092 ms 77.679 ms 82.097 ms
6 win-bb3-link.ip.twelve99.net (62.115.114.184) 160.339 ms 163.365 ms 159.595 ms
7 ffm-bb2-link.ip.twelve99.net (62.115.138.22) 160.000 ms 167.966 ms
ffm-bb1-link.ip.twelve99.net (62.115.137.202) 152.954 ms
8 * prs-bb2-link.ip.twelve99.net (62.115.122.138) 152.577 ms 148.584 ms
9 ldn-bb1-link.ip.twelve99.net (62.115.135.24) 82.147 ms 87.854 ms
ldn-bb4-link.ip.twelve99.net (62.115.114.228) 79.340 ms
10 nyk-bb1-link.ip.twelve99.net (62.115.112.244) 146.408 ms
nyk-bb2-link.ip.twelve99.net (62.115.113.20) 148.321 ms 157.793 ms
11 nyk-b3-link.ip.twelve99.net (62.115.115.9) 151.642 ms 150.373 ms
nyk-b3-link.ip.twelve99.net (62.115.139.151) 148.823 ms
12 digitalocean-ic306498-nyk-b3.ip.twelve99-cust.net (62.115.45.10) 159.121 ms 152.511 ms
digitalocean-ic306497-nyk-b3.ip.twelve99-cust.net (62.115.45.6) 149.473 ms
Не вижу, но может не знаю где смотреть.
Неделю назад пришёл терминал (Польша). Работает стабильно 300/30 Mbit/s.
Примечательно так же то, что gcc 7.1.1 не сгенерировал оптимальный код, только clang 4. Gcc почему-то постеснялся использовать , а вместо этого сгенерировал 2 инструкции
Во втором случае запись зависит от сравнения, и не может начинаться раньше, чем выполнилось сравнение, которое в свою очередь дожидается результата ldrb. Вот и вся разница, счетчик либо указатель тут не причем.
Оптимизированный вариант neon_despace_branchless не зависит от процента пробелов.
Где dspace это:
dspace_ptr:
Как видно из результатов код с меньшим количеством инструкций выполняется медленнее. Подробное объяснение потянет на отдельную статью, но если кратко, то важно не только количество инструкций, но и зависимости между ними, и это то, что компилятор умеет выстраивать достаточно не плохо, если ему не мешать. Например на Cortex A7 пара ldr/str для одного и того же регистра выполняется столько же, сколько простой ldr.
Также
выполнятся за 1 такт потому что поддерживается dual issue для инструкций читающих по одному регистру.
Вот так, примерно, будет выглядеть выполнение кода по тактам:
1 ldrb r2, [r3]!, #0x1
2 strb r2, [ip, r0]
3 cmp r2, #0x20
4 addhi r0, r0, #0x1
4 subs r1, r1, #0x1
5 bne loc_10554
1 ldrb r1, [r3]!, #0x1
3 cmp r1, #0x21
4 strbhs r1, [ip]!, #0x1
5 cmp r3, r2
6 blo loc_10584
Выводы.
В ARM64 больше нету условного выполнения каждой инструкции, есть b, и csel.
Вот arm64, 11я строчка csinc для примера из статьи и 49я csel и 45я mov для if.
despace(buffer, N): 0.79 ns per operation
despace3(buffer, N): 1.19 ns per operation
if вместо тернарного оператора генерирует код через csel + move вместо csinc, что делает цикл на одну инструкцию больше (7 vs 6). Вообще заменять работу с массивами и индексами на указатели — только мешать компилятору оптимизировать.
Вот результаты:
despace(buffer, N): 0.79 ns per operation
despace2(buffer, N): 1.40 ns per operation
1) Превращение индекса в указатель занимает 0 инструкции (ldrsb w10, [x9], #0x1)
2) Безусловная запись быстрее чем условная, т.к. нет лишнего условного перехода.
3) Проверка генерирует дополнительный условный переход. в то время как тернарный оператор дает одну безусловную инструкцию cinc x0, x0, gt.
В итоге во втором варианте больше ветвлений, которые портят конвейер.