DIV! Ryzen 3600X VS Xeon 5450

Тема в разделе "WASM.ASSEMBLER", создана пользователем Intro, 31 июл 2023.

  1. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    563
    Лазил тут по ютубу, и наткнулся на тест Питухон, Ассемблер, С. И там была простая программка по нахождению простых чисел в заданном диапазоне. Пример на ассемблере был на отвратительном АТиТ синтаксисе, переделал на UASM. Начал тестировать, сначала хеон 5450, потом дома свой райзан 3600х, а потом старый атлон 2 х4 640 3ГГц. И самый быстрый оказался... Хеон, потом райзен и аутсайдер атлон.
    Входное значение 10000000, время.
    Хеон 5450 3ГГц: 6000 мс*
    Райзен 5 3600Х 3.8 ГГц буст 4.2-4.4 ГГц: 8104 мс
    Атлон II х4 640 3ГГц: 24384 мс
    Результат: 664579, кол. делений 1746210134
    Вспоминаю по памяти тот ПК на работе, потом уточню.
    В общем, не понял чего старый интыл так уделывает амд, причём относительно новые. Понятно в программе именно DIV занимает основное время выполнение.
    --- Сообщение объединено, 31 июл 2023 ---
    Вот скрины.
     

    Вложения:

    • Primes(v0.02).rar
      Размер файла:
      878 байт
      Просмотров:
      94
    • Athlon640.png
      Athlon640.png
      Размер файла:
      24,8 КБ
      Просмотров:
      97
    • Ryzen3600X.png
      Ryzen3600X.png
      Размер файла:
      16,8 КБ
      Просмотров:
      88
  2. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    млин, чем он отвратителен? :)))
    значит параллелит функцию. :)
     
  3. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    563
    Вот скрин 5450. Частота меньше чем у рязани, а скорость быстрей, значит деление кора 2 делает значительно быстрей чем относительно новый zen2.
    Тут не про параллеливание, а почему АМД так медленно делают деление.
     

    Вложения:

    • Xeon5450.png
      Xeon5450.png
      Размер файла:
      46,4 КБ
      Просмотров:
      94
  4. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    ну-так возьми и сделай многопоточку - вот и увидишь деление там дюже шустрое али нет :) да, и ваще - деление везчь весьма болезненная в плане скорости, тч амудя бы тоже взяли бы себе столь шустрый алго.. такое не засекретишь :grin:
     
  5. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    563
    Оптимизировал алгоритм.
    Код (ASM):
    1.  
    2. ;; простые числа v0.03
    3. .386
    4. .model flat, stdcall
    5. option casemap:none
    6. include msvcrt.inc
    7. include macros.asm
    8. .data?
    9. CountDIV        dword ?
    10. tblPrimes       dword 8000 dup(?)   ;6542
    11. .code
    12. align_proc
    13. isPrimes proc (dword) uses edi n:dword
    14.     mov     edi, n
    15.     mov     eax, n
    16.     .for (ecx=2: ecx<eax: ecx++);
    17.         mov     eax, edi
    18.         xor     edx, edx
    19.         inc     CountDIV
    20.         div     ecx
    21.         .if (edx==0)
    22.             xor     eax, eax
    23.             ret
    24.         .endif
    25.     .endfor
    26.     mov     eax, 1
    27.     ret
    28. isPrimes endp
    29. align_proc
    30. isPrimes2 proc (dword) uses edi n:dword
    31.     mov     edi, n
    32.     mov     eax, n
    33.     .for (ecx=0: tblPrimes[ecx*4]<eax: ecx++);
    34.         mov     eax, edi
    35.         xor     edx, edx
    36.         inc     CountDIV
    37.         div     tblPrimes[ecx*4]
    38.         .if (edx==0)
    39.             xor     eax, eax
    40.             ret
    41.         .endif
    42.     .endfor
    43.     mov     eax, 1
    44.     ret
    45. isPrimes2 endp
    46. align_proc
    47. main proc C argc:sdword, argv:ptr ptr, envp:ptr
    48. local tm:dword, a:dword, b:dword
    49.     mov     a, 2
    50.     mov     b, 250000
    51.     .if (argc>=2)
    52.         mov     ebx, argv
    53.         .if (argc>=3)
    54.             mov     a, atoi([ebx+1*4])
    55.             mov     b, atoi([ebx+2*4])
    56.         .else
    57.             mov     b, atoi([ebx+1*4])
    58.         .endif
    59.     .endif
    60.     mov     tm, clock()
    61.     .for (esi=2, edi=0: esi<10000h: esi++)
    62.         .if (isPrimes(esi))
    63.             mov     tblPrimes[edi*4], esi
    64.             inc     edi
    65.         .endif
    66.     .endfor
    67.     xor     ebx, ebx    ;numPrimes=0
    68.     .for (esi=a: esi<=b: esi++)
    69.         add     ebx, isPrimes2(esi)
    70.     .endfor
    71.     clock()
    72.     sub     eax, tm
    73.     printf("Primes[%u, %u] = %u  time = %d ms\n", a, b, ebx, eax)
    74.     printf("CountDIV = %u\n", CountDIV)
    75.     xor     eax, eax
    76.     ret
    77. main endp
    78. main_startup3_END
    79.  
    При 100 млн время 30987 мс, результат 5761455
    Получается деление реально AMD делает медленно, т.к. эта команда редкая, и деление на константу заменяется на код с умножением, если конечно компилятор это умеет. А для быстрого деления надо задействовать дополнительные транзисторы, вот и делают попроще.
     
  6. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    в мат пакетах очень даже нередкая - акь производную найти без деления? :) пока ты не напишешь многопоточку - твои тесты неполные, пч явная многопоточка подавляет скрытую, а твои цифири на данный момент намекают именно на скрытую многопоточку ;D
     
  7. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    563
    Для Хеона 100 млн 23361 мс. Частота райзена в момент теста 4.35 ГГц, время 30987, а Хеона 3ГГц и буста нету. Получается Хеон делает деление почти в два раза быстрей: 4.35/3*30987 /23361 = 1.923. Конечно, тут можно многопоток применить, только для функции isPrimes я это делать не буду. Но возможно, есть какие-то очень хитрые оптимизации навроде деление через умножения, которые могут работать в целом быстрей, и быстрей на AMD. Но это сложно.
     
  8. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    Intro, скорость проца определяют три фактора (в расширенном случае 4е, но в данном случае рассмотрим 3)..

    1. тактовая частота.
    2. процент промахов кэша.
    3. коэффициент многопоточности.
    ----------
    УсЁ :) в твоём случае промахи кэша можно исключить - отсюда и выщимляем скрытую многопоточку да никаких супер-пупер секретных алго деления чисел у ЫнтЭля няма от слова СОВСЕМ :grin:
     
  9. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    есть, кстати, простой способ как скрытую многопоточку вышибать - нужно поднагрузить проц левым процессом и запускать тест: скрытая многопоточка при росте загрузки проца очень сильно проседает. :)
     
  10. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    563
    Вот немного переделал, таблица теперь макросом создаётся. Время компиляции уже нормальное такое 5.4 сек на райзене.
    Код (ASM):
    1.  
    2. ;; простые числа v0.04
    3. .386
    4. .model flat, stdcall
    5. option casemap:none
    6. include msvcrt.inc
    7. include macros.asm
    8. IS_PRIMES MACRO N:req
    9. LOCAL i,r,res
    10.     r = N
    11.     i = 2
    12.     res = 1
    13.     WHILE i LT r    ;;i < r
    14.         IF (N MOD i) EQ 0
    15.             res = 0
    16.             EXITM
    17.         ENDIF
    18.         r = N / i
    19.         i = i + 1
    20.     ENDM
    21.     EXITM <res>
    22. ENDM
    23. INIT_PRIMES MACRO tbl_name:req, max:req
    24. LOCAL i
    25.     tbl_name    dword 2
    26.     i = 3
    27.     WHILE i LE max  ;;i <= max
    28.         IF IS_PRIMES(i) EQ 1
    29.             dword i
    30.         ENDIF
    31.         i = i + 2
    32.     ENDM
    33. ENDM
    34. .data?
    35. CountDIV        dword ?
    36. .const
    37. INIT_PRIMES     tblPrimes, 10000h   ;6542
    38. .code
    39. align_proc
    40. isPrimes proc (dword) uses edi n:dword
    41.     mov     edi, n
    42.     mov     eax, n
    43.     .for (ecx=0: tblPrimes[ecx*4]<eax: ecx++);
    44.         mov     eax, edi
    45.         xor     edx, edx
    46.         inc     CountDIV
    47.         div     tblPrimes[ecx*4]
    48.         .if (edx==0)
    49.             xor     eax, eax
    50.             ret
    51.         .endif
    52.     .endfor
    53.     mov     eax, 1
    54.     ret
    55. isPrimes endp
    56. align_proc
    57. main proc C argc:sdword, argv:ptr ptr, envp:ptr
    58. local tm:dword, a:dword, b:dword
    59.     mov     a, 2
    60.     mov     b, 250000
    61.     .if (argc>=2)
    62.         mov     ebx, argv
    63.         .if (argc>=3)
    64.             mov     a, atoi([ebx+1*4])
    65.             mov     b, atoi([ebx+2*4])
    66.         .else
    67.             mov     b, atoi([ebx+1*4])
    68.         .endif
    69.     .endif
    70.     mov     tm, clock()
    71.     xor     ebx, ebx    ;numPrimes=0
    72.     .for (esi=a: esi<=b: esi++)
    73.         add     ebx, isPrimes(esi)
    74.     .endfor
    75.     clock()
    76.     sub     eax, tm
    77.     printf("Primes[%u, %u] = %u  time = %d ms\n", a, b, ebx, eax)
    78.     printf("CountDIV = %u\n", CountDIV)
    79.     xor     eax, eax
    80.     ret
    81. main endp
    82. main_startup3_END
    83.  
    Нет, многопоточность тут точно не причём, Хеон умеет просто делить быстрей, деление штука не простая, раньше делалась вообще медленным микрокодом, сейчас всё равно используется микрокод, но там аппаратные ускорители используются. У АМД таких быстрых ускорителей нет, вот и вычисляет в два раза медленней, зато транзисторный ресурс для чего-то более важного задействован.
     
  11. algent

    algent Member

    Публикаций:
    0
    Регистрация:
    11 апр 2018
    Сообщения:
    101
    Это не главное. Самый важный фактор, это сколько контроллеров памяти в процессоре. По простому, сколько можно подключить планок памяти.
    >> 2. процент промахов кэша.
    А это скорее зависит от качества софта. Например:
    var1 = byte_table[0]; // Читается некая линия кэша. При кэш-промахе, выполняется цикл шины.
    var2 = byte_table[64]; // Читается другая линия кэша. При кэш-промахе, опять выполняется цикл шины.
    var3 = byte_table[128]; // Читается третья линия кэша. При кэш-промахе, снова выполняется цикл шины.
    цикл шины - это плохо(долго). Выставляется адрес ряда, потом латенси, потом выставляется адрес колонки, снова латенси, потом R или W.
    Около 10 клоков памяти, не проца! Клоки памяти медленнее. Дикий оверхед одиночной R или W можно снизить, если читать/писать бёрстами, например подряд 8 чтений, из 8 подряд идущих ячеек памяти(columns). 8 чтений * 8 байт(64 бита - шина данных контроллера памяти) = 64 Байта. Интел не изобретал линию кэша, это тупо снижение оверхеда. Но бл**ь, смотрим три строки кода выше. Обычно программеры от балды размещают поля внутри структур, и структуры друг за другом. Худший случай, когда читаются N*64 Байта, ради N переменных.
    Кстати, тут недавно чел рекламировал компилер Интела. Были казалось бы дикие цифры: код собранный компилятором MS выполнялся более 5 минут. Код собранный компилятором Интела выполнялся 23 секунды. Имхо, это возможно, особенно на подобранной задаче. Компилятор оценивает трассу, работу с памятью на ней и внаглую посылает нах, выбранное программистом расположение данных в памяти и перетасовывает их по своему. Ради минимизации кэш-промахов. Ну это конечно актуально при работе на огромных массивах данных.
     
  12. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    124
    Контроллёр памяти всегда читает только в пакетном режиме, и параметр "Burst Lenght" (в вашей терминологии бёрсты) у него жёстко запрограммирован значением(8). На старых чипсетах можно было менять BL=4 или 8, но начиная с PCH этой опции уже нет.

    MCU.png

    Поэтому за одну транзакацию ЦП (с шиной памяти 8-байт, и линейкой кэша 64-байт) всегда читает из ОЗУ мин. 8х8=64 байта, но при старте приложения кэш(1) заполняется сразу под завязку. То-есть если запустить код ТС размером в пару кило, то весь он целиком разместится в кэш, и обращений к памяти больше не будет от слова совсем. Если-же в процессе работы приложению понадобятся доп.данные свыше размеров кэша L2/3, то только тогда уже ЦП запросит у контроллёра BIU (Bus Interface Unit) шину для доступа к ОЗУ - именно на данном этапе и всплывает наружу проблема выравнивания данных в памяти. Доступ к кэшу осуществляется по иным правилам, и выравнивание здесь не играет абсолютно никакой роли.
     
    Mikl___ и UbIvItS нравится это.
  13. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    360
    DDR5 уже BL16 умеет
     
  14. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    124
    Ну так контроллёры серверных плат в режиме "BL=Page" способны читать вообще вcю открытую DRAM-страницу. Чтобы посчитать объём данных за одну/такую транзакацию, нужны 2 параметра: кол-во чипов в модуле памяти (обычно 8), и кол-во бит для адресации столбцов.

    У меня модуль 2ГБ, и столбцы в матрице памяти одного чипа адресуются 10-битами, а строки 14-битами. То-есть получаем рулон туалетной бумаги шириной 210=1024 байт, и высотой в 214=16384 строк. Итого: 16384 × 1024 = 16 Мб. Дальше расчёты:

    1. 16М ×8 (кол-во банков) = 132 Мб ёмкость одного чипа.
    2. 132М ×8 (кол-во чипов в модуле) = 1 ГБ на одной стороне (см.ранк).
    3. 1Гб ×2 (кол-во ранков) = 2 ГБ ёмкость всего модуля памяти.

    Таким образом, в 1-ой строке 1-го чипа 1К данных. Соответственно если чипов всего 8, то в моём случае получаем DRAM-строку размером 8КБ. Операция чтения ОЗУ начинается с того, что контроллёр выставляет на шину адрес строки, и подтверждает его стробом RAS#. По этому стробу логика DRAM открывает нужную строку, но т.к. адресные линии разводятся по чипам параллельно (а линии данных последовательно), то указанная строка открывается сразу во всех 8-ми чипах памяти! Эту глобальную строку и назвали "DRAM-страницей".

    Mem.jpg

    Открытие страницы приводит к тому, что все её байты сваливаются в усилители "Sense-AMP" (у каждого чипа он свой). Теперь контроллёр выставляет на шину адрес столбца. При этом 3 мл.бита адреса принудительно сбрасываются в нуль так, что номер столбца всегда кратен 8-ми. Такой алго решает проблему чтения ОЗУ по нечётным адресам и границе 8-байт.

    Если режим BL=8, то получаем цикл х8 чтения данных из усилителей "Sense-AMP" начиная с выровненного адреса. Если-же режим BL=Page, то в кэш ЦП копируется сразу вся открытая DRAM-страница размером 8К. Транзакация завершается тем, что данные из "Sense-AMP" перемещаются опять на свои места в матрицу памяти.
     
    Mikl___ нравится это.
  15. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    Intro, ну-лады, тогда расскажи/опиши (пожалуйста) алгосы амуди и ынтэля.. интересно же-ж :)
     
  16. algent

    algent Member

    Публикаций:
    0
    Регистрация:
    11 апр 2018
    Сообщения:
    101
    Странно это. Не читал спецификации на контроллёры памяти Интел, но делал сам контроллёр SDRAM на ПЛИС. Уж full-page burst иметь надо. Например в MSR_MISC_FEATURE_CONTROL есть бит DCU который разрешает/запрещает Hardware Prefetcher который сразу тащит вторую линию кэша, ну, т.е. тащит две. Дополнительные PRECHARGE, ACTIVE, ну там RAS, CAS - нафига они нужны??

    "сразу под завязку" заполняется L1 code. А чем "сразу под завязку" заполняется кэш данных?? И при чём здесь ТС? Посмотрите пост
    и мой ответ на него. Кажется надо пояснить подробнее:
    Код который я привёл, читает 192 байта, но реально, на тот момент времени нужны три байта. В остальных 189 программер разместил нужные переменные, но они понадобятся не скоро. Лишь несколько из них, понадобятся после того как эти три линии кэша будут затёрты другими переменными из этих окрестностей. Потом опять и опять, несколько других, понадобятся после того как эти три линии кэша снова будут затёрты. И будут многочисленные чтения 192 байт(циклы шины!!!), каждый раз ради 3(ну 5 или 10, или 20) байт. Цитирую себя :dntknw:
    Это конечно худший случай.

    Это адресовано мне ?? Если да, то не понял о чём вы. При чём здесь "выравнивания данных", каким оно здесь боком??
     
  17. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    124
    Вы видимо рассматриваете какой-то процессор от Dendy - в современных х86 дела обстоят немного иначе. На старте приложения заполняются сразу оба кэша(L1), и Inst и Data. После того как в L1d считали 192-байта (из примера выше), запись в них происходит локально, т.е. в кэш-линейки, а не в ОЗУ. Поэтому не будут никаких "многочисленных чтений 192 байт (циклы шины!!!) каждый раз ради 3 байт" . Иначе зачем вообще нужен такой кэш, если при каждом чихе, ЦП вынужден лезть чз внешнюю шину в ОЗУ? На этот факт и опирается реклама производителей типа: "Попаданий в кэш ~95%, против 5% промахов".

    Кэш доступен для записи, и когда ЦП встречает в коде операцию(W) в память, то фактически модифицирует только данные в L1d. Исключением являются лишь ситуации, когда софт взаимодействует с внеш.устройствами посредством DMA (прямой доступ к ОЗУ, минуя ЦП). В этом случае в игру вступает механизм "Поддержки когерентности", который проводит валидацию данных в кэшах и ОЗУ. Если в кэш-строке взведён бит(М), она считается модифицированной и алгоритмом Write-Back (отложенная запись) выгружается из кэш в ОЗУ.
     
    Последнее редактирование: 5 авг 2023
    Mikl___ нравится это.
  18. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    563
    Да хрен его знает. Для меня ассемблер это реально высокоуровневый объектно-ориентированный ЯП, где есть такая штука как сокрытия данным/реализации. Процессоры давно не выполняют х86 код, где-то с первых пентаков или даже 486, этот код сначала транслируется в RISC-подобные команды которые потом выполняются АЛУ, которых несколько от 2-4 или даже 5 для целых, и ещё сколько там для FPU, SSE, AVX. При этом этот транслятор умеет делать оптимизации налету, так что программирования на ассемблере снова актуально, этот самый встроенный оптимизатор оптимизирует ваш сраный говнокод на ассемблере на лету.
    Какой алго для делений AMD/Intel я конечно не знаю, но Хеон серверный процессор и там может быть какие-то особые реализации. Но команда DIV, IDIV выполняется реально редко, оптимизаторы стараются их убирать, вот амд решили сделать это деление попроще, чтобы запас транзисторов задействовать на что-то более важное.
     
  19. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.116
    Intro, мне всё же непонятно - с одной стороны ты говоришь..
    потом..
    короче, погружайся https://en.wikipedia.org/wiki/Division_algorithm#Non-restoring_division
    без параллельки == НИКУДА :)
    --- Сообщение объединено, 5 авг 2023 ---
    ежли так поступать - процент промахов будет близок к 100% (прожка даже своими потоками может флудить кэш по бестолковке) :)
     
  20. algent

    algent Member

    Публикаций:
    0
    Регистрация:
    11 апр 2018
    Сообщения:
    101
    Я просто приведу код:
    for(i=0; i < 0x1000000; i += 6 * 64)
    {
    var1 = byte_table;
    var2 = byte_table[i+128];
    var3 = byte_table[i+256];
    }
    for(i=0; i < 0x1000000; i += 6 * 64)
    {
    var1 = byte_table[i+1];
    var2 = byte_table[i+129];
    var3 = byte_table[i+257];
    }
    for(i=0; i < 0x1000000; i += 6 * 64)
    {
    var1 = byte_table[i+2];
    var2 = byte_table[i+130];
    var3 = byte_table[i+258];
    }
    .....
    Смещение +128(две линии кэша) сделал, предполагая что Hardware Prefetcher включен и тащится две линии кэша. Т.е. читается 384 байта ради трёх. В каждом цикле, все кэши затираются много раз.

    Я тщательно изучал главу 11 Memory Cache Control, 3го тома. Надо для работы. Кстати, про взаимодействие PCH и процессора, удалось найти очень мало. В четырёхтомнике. Почти всё что есть, приведено на рисунке 11-2 :bad:. А десятитомник Интела, в России не получается скачать, даже через Tor.

    --- Сообщение объединено, 6 авг 2023 ---
    Ну специально ведь никто не станет так делать. Хотя, вспоминаются истории, когда после апдейтов замедлялось старое железо :).
    --- Сообщение объединено, 6 авг 2023 ---
    Ошибся. Конечно надо - var1 = byte_table;
    --- Сообщение объединено, 6 авг 2023 ---
    Блин :) Ещё раз
    var1 = byte_table;
    --- Сообщение объединено, 6 авг 2023 ---

    --- Сообщение объединено, 6 авг 2023 ---
    Глюк :dntknw:
    --- Сообщение объединено, 6 авг 2023 ---
    ;