Pull to refresh
-5
@veslemayread⁠-⁠only

User

Send message
llvm-ir — это внутреннее представление кода, которое может быть скомпилировано в машинный код для целевой платформы (либо JIT-исполняться).

WASM — это тоже самое.

Как я понимаю, идея WASM, что он переносимый

Да, как и llvm-ir. Но переносимость это главное — главное изолированность.

Который затем должен быть интерпретирован/JIT-компилирован/компилирован целиком перед запуском в реальный машинный код платформы, на которой он запускается, верно?

Ну интерпретацией его никто заниматься не хочет. А так да — как ir.

В статье речь идет о запуске WASM в том числе и вне браузера.

Ну да, для целей похожих на броузер. Платформонезависимость + изолированность. Два кита.

если и так есть имплементации JVM почти под что угодно

Ну не под что угодно, а под конкретный набор платформ. Это просто бинарник, который не исполняется в изолированной среде. Ваша jvm может делать с системой что угодно.

А далее вам понадобится изоляция, а ещё и платформо-независимая. И вы сделаете очередную vm для нативного кода. Но тут всё сделано уже за вас.

и наше jvm-приложение таким образом УЖЕ переносимо?

Нет, вашего jvm-приложения на уровне платформы не существует — они никому не интересно. Это просто какая-то локальная логика jvm. Приложением является jvm и она не переносима.

К тому же, кейсы использования wasm«а выходят за „одно приложение“. Там обычно миллионы приложений. jvm/.net/js/python — тысячи их. Везде и всюду какие-то непонятные окружения, бинарники, платформа-специфичная структура.

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

Профит очевиден. Юзеру ненужно ставить себе тысячи рантайма тысяч языков. Вам ненужно собирать свой рантайм под тысячи платформ, чтобы таскать его с собою. Юзеру ненужно заботится о том, что ваша jvm может быть заражена и украсть его данные/сломать ему систему.

Он просто ставит заранее надёжный wasm-рантайм а далее его ничего не волнует. Что там у вас, как там у вас. Он просто использует что угодно, где угодно, безопасно.

Я не имел в виду поддержку GC непосредственно в WASM, я имел в виду как раз

Правильно. Если вам нужен ГЦ в программе на С++ — вы пишите гц на С++. Это не вызывает никаких проблем и С++ ненужна нативная поддержка гц. Васм такой же С++.

Очень даже есть. Например, .NET GC реализован как часть CoreCLR (если говорим о .NET Core), написан на C++, как большинство CoreCLR.

Ну С++ — это внешний для .net мир. Я имел ввиду нативно в контексте .net. Ведь именно потому гц написан там на С++.

Именно поэтому .net и нужен С++ и нативнища, ведь байткод и vm там высокоуровневые. Wasm же — это совершенно другой случай.

Это, конечно же, очень круто, но только эта платформа не аппаратная, абстрактная виртуальная машина.

llvm-ir тоже абстрактная виртуальная машина. Ключевым тут является не название, а смысл. Какого уровня эта машина и насколько полно она может обобщать эту аппаратную платформу.

И есть WASM-runtime, который занимается исполнением всего этого дела. Поэтому как не крути, это медленнее.

WASM-runtime не занимается исполнением кода. Он компилируемый, как и ir. Да, этому коду(как и ir) требуется некий рантайм, но в чём проблема? Никто и не говорил, что это бесплатно.

К тому же, вы предлагали(насколько я понял) заменить это жава/дотнет байткодом, который ещё более медленный, на порядки более медленный. И для решения этой проблемы там есть специальный jit, который воспринимает этот байткод не как байткод, а как некий язык высокого уровня.

ir и, в меньшей степени, wasm — уже оптимизируются на уровне байткода и транслируются в бинарник почти 1в1.

И идея подложить слой абстракции под абстракцию кажется мне весьма сомнительной.

Вы воспринимаете jvm/.net по своему, но всё это лишь обычные программы на том же C (C++/Rust/etc).

И этот слой нужен для изоляции, ведь ОС так же вносит множество оверхеда, как и все аппаратные средства изоляции в железе(процессоре). На самом деле да, в какой-то мере wasm и броузер заново изобретают ОС и работают хуже. Взять ту же изоляцию памяти. Но броузер не может стать ОС потому что уже есть ОС, есть вендорлок и никто никуда не уйдёт и никого никуда не пустят.

К тому же изоляция уровня ОС достаточна слаба и броузер(либо какая-то другая песочница) всё равно нужен и это так же будет накладывать множество ограничений. В том числе и по производительности.

В таком ключе я вижу целевое применение WASM исключительно в переносимом производительном коде, написанным на C/C++/Rust/etc. Что, имхо, довольно ограниченная ниша.

jvm/.net — это такой же С++ код как и любой другой.

И в этом его смысл. Мы можем в броузере(в какой угодно другой песочнице) запустить любой C/C++/Rust/etc-софт, а это значит, что мы можем запустить и jvm и v8 и что угодно. Да, это будет медленнее, но.

Смысл в том, что это будет работать и работать хорошо. Причём максимально безопасно. Вы сможете загрузить любой нужный вам рантайм к пользователю в броузер и делать так что хотите. Какая вам разница с того, что нативная jvm будет работать быстрее? Считайте, что у пользователя просто процессор похуже.

Мы сделали JS, чтобы запускать код в браузере

Хоть жс и хорош, но он был ошибкой. Причины тому просты — никто не думал, что это выльется в полноценные приложения на жс. Броузер стал некой новой ОС и такой высокоуровневый язык очень плох как базовый язык платформы.

потом сделали WebAssembly

Правильно. Это попытка исправить проблему.

чтобы запускать его быстрее

Быстрее — это просто следствие. Задача была «сделать максимально близкое к железу представление, которое может всё то — что может железо».

А потом мы захотим писать на чем-нибудь более удобном, чем C/C++/Rust/итп

А это неважно. Тут фишка в том, что вы на си можете написать питон, а на питоне си нет.

например, хотим сборку мусора

Мы просто берём и пишем любую сборку мусора.
и притащим GC поверх WASM

Нет. Ненужно ничего тащить. Вы, скорее всего, неправильно поняли. Когда говорят «тащить гц в васм» — это говорится именно про доступ из васма к js-объектам. Т.е. налаживания коммуникации между двумя отдельно запущенными в броузере vm. Некий такой ffi.

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

когда в .NET/JVM/итп он реализован нативно.

Нету там никакого нативно и в этом проблема. wasm — это именно llvm-ir, а не жава/дотнет-байткод. Об их фундаментальной разнице я говорил выше. Ни на каком жава/дотнет-байткоде вы jvm не напишите, а вот на ir/wasm вполне.

В этом фишка. Вы берёте базовый язык — это такой язык, который может «всё». А далее пишите что хотите, хоть jvm, хоть питон.

Тут уже сделали Blazor — веб-фреймворк на C#, путем портирования Mono на WASM.

Да, это так и работает. Так и задумывалось. Люди правильно сделали.

Мы портировали виртуальную машину на виртуальную машину, чтобы ты мог запускать виртуальную машину, пока запускаешь виртуальную машину (с).

Нет. Вы путаете слова и понятия. vm в разных контекста значит совершенно разное, ведь любая ОС — это по-сути vm.

Воспринимайте wasm как ОС + некую отдельную платформу(аппаратную). И уже далее на этой ПА платформе вы запускаете нативные программы. Такой нативной программой является mono. И люди её запустили. А ваш C#-код и его окружение — это уже ваши внутренние с .net/mono заморочки. wasm это не интересует, как и любую другую платформу(нативную или около того).

чем оно будет отличатся от имеющихся технологий? (.net, JVM).

Намного эффективнее(он более низкоуровневый), нет привязки к рантайму(гц, операции над объектами и прочее).

Или даже clang умеет генерить не привязанный к конкретной архитектуре код (флаг -emit-llvm )

llvm не совсем умеет отвязывать ir от архитектуры. База там действительно общая, но существует много всякой платформо-специфичной атрибутики. В целом ir почти во всём лучше wasm, но.

Основная фишка wasm — это именно изолированное(и эта изоляция намного ниже, нежели в ситуации с той же java) исполнение кода. Очевидно, что брать и генерировать asm — это не лучшая идея. Сам же llvm хоть и называется vm — vm не является.

Т.е. wasm — это такое безопасный ассемблер, который исполняется в вм. Он компилируемый, простой, крайне низкоуровневый, но он не позволяет читать/писать куда не надо. Это его основная задача и фундаментальное отличие от всего остального.

Сомневаюсь, что какой-то адекватный вендор будет просить отключить эту фичу. Скорее он будет просить отключить always.

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

Что за глупости? Какое отношение все это имеет к ядру?

Выделение и освобождение памяти происходит с помощью вызовов malloc и free соответственно

Нет. В линуксе(да и везде) маллок/free не занимаются памятью, а работают только с виртуальным адресным пространством.

которые в итоге будут обработаны системными вызовами ядра, а значит отобразятся в утилизации CPU как системное время.

Не всегда, очень даже не всегда.

В большинстве современных операционных систем

К операционным системам это не имеет никакого отношения. Они лишь использует механизмы предоставленные железом.

виртуальная память организуется с помощью страничной адресации

Не виртуальная, как минимум не только виртуальная.

при таком подходе вся область памяти делится на страницы фиксированной длины

Память.

и при выделении, например, 2 Гб памяти, менеджеру памяти придётся оперировать более чем 500000 страниц

Нет. Менеджер памяти тут вообще не при делах и работает он, как я уже сказал, с адресным пространством. Т.е. он выделит просто диапазон и повесит на него права.

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

В таком подходе появляются большие накладные расходы на управление и для их уменьшения были придуманы технологии Huge pages

Не для этого.

с их помощью можно увеличить размер страницы

Это аппаратная фича.
например до 2МБ, что существенно сократит количество страниц в куче памяти.

Не для этого.

В некоторых случаях THP вызывает ничем не мотивированное увеличение потребления CPU в систем.

Не в некоторых, а вполне в конкретных. И всё там мотивировано.

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

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

Дак для чего же нужны эти большие страницы? Всё очень просто. Абсолютно неважно — сколько там страниц, пока они влезают в кэш. Есть специальный кэш — называется tlb. Когда происходит промах(т.е. отображения нету в кэше) — процессор вынужден пойти(автономно — ЭТО НЕ СИСТЕМНОЕ ВРЕМЯ) — найти и прочитать это отображение из памяти(если не нашел — тогда уже триггерится пейджфолт и уже тогда включается ядро).

Если отображение уже существует в памяти, то это на трупут почти не влияет. Процессор умеет в префетч и заранее подготавливает все отображения. Да, это съедает чуть-чуть трупута памяти(т.к. отображения читать из памяти нужно), но это крохи.

Но проблемы возникают именно на random access, когда никакой префетч не работает и трупут упирается в летенси(помноженной на блок) памяти. Поиск отображения — очень дорогая по летенси операция и от того задержки памяти возрастаю в разы.

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

У этого подхода существует только одна проблема — это разряженная память. Дело в том, что по-умолчанию ядро попросту разрешает r/w в какой диапазон вадресспейса. Памяти в «памяти» выделяемой маллоком нет. Вообще(если это большие блоки(100к и больше по умолчанию, насколько я помню)).

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

Если мы будем читать/писать память байт за байтом, то первое чтение затригеррит пейджфолт и ядро свяжет первую(нулевую) страницу нашего отображения с реальной страницей(физической памяти). Для последующих 4096 байт(включая текущие чтение/запись) память будет. После — её не будет и опять будет пейджфол.

Тут уже можно понять — в чём проблема. Если мы выделим 2 мегабайта памяти и запишем половину — в ситуации с 4к страницами — выделится 1 мегабайт физической памяти. В ситуации с 2м страница — 2 мегабайта. Подобная ситуация сплошь и рядом в том же С++.

Тоже самое с разряженными массивами. Если у нас есть гигантский массив и мы сделаем туда 10 записей — мы затриггерем в среднем 10 пейджфолтом и свяжем 10 страница по 4к. Если тоже самое мы сделаем с 2м страницами — будет 10 по 2м.

Откуда может взяться высокий systime? Всё очень просто. Пейджфолты сами по себе дорогие, но разницы между типа страниц почти нет. Но.

Дело в том, что ядро при связывании страницы ОБЯЗАНО её забить нулями, т.е. пройтись мемсетом. Очевидно, что если мы в С++ запишем в строку 1гб + 1 байт, то ядро сделает мемсет на 1гб + 4к в случае с 4к страницами и 1гб + 2м в случае с 2м. И 1гб + 1гб в случае с 1гб страницами. Этим же обусловлено и возросшее потребление памяти.

И если в софте очень много разряженной памяти, то очевидно — ему повсеместные 2м+ страницы будут вредны, а в современном железе там 1гб и более страницы.
Лучше сделать:

echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
Чтобы дать приложениям возможность использовать фичу явно, если она им нужна.
Судя по вики ваш процессор может в:
cat /proc/cpuinfo | grep pdpe1gb

Но почему-то их у вас нет.

Информация с perf абсолютно бесполезна. Нужно взять С++/раст-версию и запустить её на 1ккк вначале на never, а потом на always.

#!/usr/bin/env bash

set -e -o xtrace

cat /proc/cpuinfo
cat /proc/meminfo

sudo bash -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled"
perf stat -dddd ./cpp-single $@

sudo bash -c "echo always > /sys/kernel/mm/transparent_hugepage/enabled"
perf stat -dddd ./cpp-single $@

Как-то так.

Какая ошибка? Нет никакой ошибки. Результаты аналогичные.
Проверил ещё раз:
Версия гцц:
gcc (Gentoo 8.3.0 p1.0) 8.3.0

Код автора против кода на расте из комментариев.
rust(100kk):
249511591

real 0m5,588s
user 0m5,545s
sys 0m0,043s
cpp(100kk):
249511591

real 0m5,188s
user 0m5,134s
sys 0m0,045s
rust(100kk):
249511591

real 0m5,613s
user 0m5,572s
sys 0m0,040s
cpp(100kk):
249511591

real 0m5,217s
user 0m5,173s
sys 0m0,043s
rust(100kk):
249511591

real 0m5,620s
user 0m5,580s
sys 0m0,040s
cpp(100kk):
249511591

real 0m5,192s
user 0m5,151s
sys 0m0,041s
rust(100kk):
249511591

real 0m5,596s
user 0m5,556s
sys 0m0,040s
cpp(100kk):
249511591

real 0m5,206s
user 0m5,161s
sys 0m0,044s
rust(1kkk):
2497558338

real 1m7,324s
user 1m6,325s
sys 0m0,995s
cpp(1kkk):
2497558338

real 1m2,467s
user 1m2,069s
sys 0m0,395s
rust(1kkk):
2497558338

real 1m7,135s
user 1m6,739s
sys 0m0,392s
cpp(1kkk):
2497558338

real 1m2,446s
user 1m2,019s
sys 0m0,423s
rust(1kkk):
2497558338

real 1m7,527s
user 1m7,133s
sys 0m0,390s
cpp(1kkk):
2497558338

real 1m2,494s
user 1m2,051s
sys 0m0,440s
rust(1kkk):
2497558338

real 1m7,133s
user 1m6,754s
sys 0m0,375s
cpp(1kkk):
2497558338

real 1m2,530s
user 1m2,139s
sys 0m0,383s



А в хаскеле есть какой-то дефолт?

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

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

Нет, речь шла не об этом. Речь шла о том, что вы вначале написали РАБОЧИЙ вариант на хаскеле(так, как привыкли), а далее началась борьба с его проблемами. И вы победили, в данном случае.

На С++ вы написали такой же РАБОЧИЙ вариант, но ни с чем вам воевать не пришлось. В этом разница.

Мне показалось, что у вас сложилось впечатление, будто действительно сначала пишется код с IntMap, а потом уже как-то там оптимизируется и переписывается. Это не так.

Ну почему же. Вы по-сути сделали нам ретроспективу вашей победы. В любом случае, абсолютно неважно что там — массив/не массив. Всё остальное никуда не уходит. Я не знаю как видите вы, но не могу верить вам. И я не сомневаюсь в вашей честности, просто человеку свойственно обманываться в пользу своих убеждений.

Питон, на который вы сослались.

Зачем вы врёте? Я не ссылался на питон — это вы на него ссылались. Я говорил о конкретных свойствах языков, которые пройдут этот тест — питон ему не соответствует. И вы его привели как контр-пример моим тезисам, но это не было контр-примером. Далее в своих рассуждениях на тему «почему примерно равная производительность равна»(в данном случае).

Почему Array что-то там взламывает?

Да, я уже много примеров приводил. гц/операции над массивом. Т.е. массив по-умолчанию мутабельная структура и из неё нельзя сделать иммутабельную структуру(дёшево). Ваши операции мутабельные, массив по-сути является множеством значений(переменных), что так же не особо канонично. Вы сам в этом признавались, что если бы не «готовые функции» — никакой массив я бы использовать не мог.

Вот возьмите, пожалуйста, массив. Без использования готовой функции(которая взламывает вашу концепцию) инициализируйте его своим алгоритмом.

Только код, который я пишу, остаётся чистым. Целиком. Мне не нужно пачкаться о состояние. А что там в кишках или в процессоре при этом происходит — десятое дело.

Нет, это ошибка. Ведь мы говорим об уровне языка и концепций. То, как именно пишите вы — мало на что влияет. Ведь я тоже могу писать на С++ безопасной и мой код будет безопасным, но концептуально это ничего не изменит, как и не изменит С++.

Это не матчасть, это что-то, имеющее вполне ощутимые эффекты. Например, атомики и CAS на чистом языке реализовать чуть менее тривиально, потому что у объектов больше нет адреса, их характеризующего.

Это ограничения сверху. А ограничения сверху — это то, что матчасть волнует мало. Низ первичен — верх вторичен. Низ диктует условия — верх подчиняется. Низ прорывает абстракции верха, но низу насрать на абстракции верха.

Поэтому да, у вас нету постоянных адресов и это накладывает какие-то ограничения на вас. Но это никак не влияет на матчасть. Для матчасти ваши ограничения — лишь один из множеств возможных вариантов.

Нет. В любом другом мейнстримном языке, будь то Java, C++, Go, Rust или Julia, если у вас есть переменная типа int, то в ней лежит int, а не указание, как этот int вычислить, если вдруг понадобится.

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

Поэтому, что там навешано на объект — это неважно, неважно как оно называется на уровне абстракций.

Тут было бы иронично вспомнить про SSA в процессе компиляции, но то такое.

Тут никаких противоречий нет.

Это свойства конкретной структуры данных и её взаимодействия с компилятором, но не её семантика. Хотя, может, мы по-разному определяем семантику, хз.

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

Просто подумайте. У вас есть массив с value-типом. Подошел ли вам массив другой? Не с value-типом. Он бы соответствовал определённой вами семантики, но нет — он бы вам не подошел и вы бы искали другой. Потому что на этом уровне семантика чуть шире базовой.

А, кстати, как строить графички на плюсах, вы так и не рассказали, увы :(

Не знаю. Я не занимаюсь графиками. Если что-то нужно — использую упомянутый выше gnuplot, либо какой-нибудь онлайн-сервис. Но нужно это мне это крайне редко.

Попробовал переключать на always, never и обратно, но отличия были в пределах погрешности. Или нужно было как то иначе их включать?

cat /proc/meminfo, perf stat -dddd Можете написать сюда выхлоп — разберёмся.

Вообще, я думаю, что в данном случае все немного проще.

Ну дак у меня же всё меняется(при переключении), практика соотносится с теорией.

Обслуживание ~30000 горутин априори чуть более затратно, чем четырех горутин + chan.

Это копейки на фоне всего остального. Там ниже есть мой код — там можно передать кол-во потоков. У меня на 30000потоков(на самом деле там создаётся далеко не 30к потоков, а гораздо больше) это добавляет 1-2секунды к 50 имеющимся. И это потоки ОС.

Однако из-за того что я тестирую это на процессоре 7 летней давности эта разница кажется куда более заметной для меня.

Ну я вижу с never такую же разницу, которую видите вы. Эта разница ничем, кроме влияния thp, не объясняется. По крайней мере ничем предложенным вами.

66,468976022 seconds time elapsed - go mt
64,819841304 seconds time elapsed - go
64,380398227 seconds time elapsed - go v3

64,001787006 seconds time elapsed - hs
62,100266396 seconds time elapsed - c++ + gcc
47,760004537 seconds time elapsed - c++ mt + gcc
51,243138067 seconds time elapsed - c++ mt + clang//я проверил множество раз
80 sec - nodejs(32 бита числа)

67,263350647 seconds time elapsed - rust(rustc 1.33.0)
50,179576857 seconds time elapsed - rust mt
47,933258835 seconds time elapsed - rust mt + taskset

Странно, что результат так сильно от процессора зависит.

Там дело не совсем в процессоре.Я уже писал про transparent huge pages.

У вас «многопоточность» не код ускорят, а tlb-промахи. Т.е. если отображения нету в кэше(tlb) — процессор идёт искать отображение в памяти. В случае с одним потоком — ядро(процессор) почти всё время(половину) занимается поиском. В многопотоке этот поиск размазывается по ядрам.

75,197948177 seconds time elapsed// - ваш
124,388672662 seconds time elapsed// - который "однопоточный"
66,385085639 seconds time elapsed// ваш на всех потоках.
58,259779411 seconds time elapsed// ваш, все ядра и thp


Такое впечатление, что у вас GOMAXPROCS устанавливается в 1, а не равное количеству ядер.

Нет, я бы это увидел. Ваша пилит на половине ядер, другая на всех, а ещё одна на ядре + гипетред(как и говорил автор).

А какая у вас версия Golang?

go version go1.12.1 linux/amd64

И как вы его запускали? Через go run или go build?

go run, но я не думаю, что это на что-то влияет. Там гцц собирает С++-код (миллионы строк) за 0.4sec.

Нужно искать людей с amd, либо с e5/2011 платформой. Там должен быть профит куда сильнее. На худой конец можно на амазоне тачку снять и погонять бенчмарки.
А у вас сколько ядер?

Там ненужно много ядер — оно уже двух перестаёт расти, а на 4х начинает деградировать.

Ускорение на четверть — это ж адски плохо для такого алгоритма.

Нет, с чего вдруг? Алгоритм говном — для него всё плохо.

И вот тут бы у вас, кстати, плюсы засияли, потому что вы из разных тредов в один std::vector писать можете

Нет. Дефолтная десктопная/e3 платформа штеуда утилизирует практически 70-80 трупута памяти. Поэтому никакие наивные представления про ускорение — здесь не прокатывают. Да, код подобного уровня никогда не снимет даже 10-20% трупута памяти, поэтому и существует какие-то наивные оценки на тему.

Но тут есть одна проблема. Из-за того, что алгоритм говно — получается так, что он съедая 10-20% трупута — съедает почти весь.

Причина заключается в следующим: кэш не умеет читать память байтами, 4 байта, 8 байтами, даже 32 не умеет — только кешлайнами, а это 64 байта.

Поэтому, при RA в память в подобном кейсе на каждое чтение int64_t на самом деле читает 64 байта, то же самое и с записью. Именно поэтому и наблюдает такое поведение. Код говно, алгоритм говно, а каналы до памяти все сожраны.

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

У меня все три вариации кода на го выдают примерно одинаковое время.
#include <vector>
#include <cmath>
#include <iostream>
#include <thread>
#include <atomic>

int main(int argc, char ** argv) {
  if(argc < 2) {
    std::cerr << "Usage: " << argv[0] << " maxN" << std::endl;
    return 1;
  }

  int64_t n = std::stoi(argv[1]);
  int64_t nthreads = std::stoi(argv[2]);

  std::vector<std::atomic<int64_t>> arr(n + 1);
  
  auto work = [&](size_t nthreads, size_t tnum, int64_t n) {
    for(int64_t k1 = tnum; k1 <= int64_t(std::sqrt(n)); k1 += nthreads) {
      for(int64_t k2 = k1; k2 <= n / k1; ++k2)
        arr[k1 * k2] += (k1 != k2 ? k1 + k2 : k1);
    }
  };
  
  auto run = [&](size_t nthreads) {
    std::vector<std::thread> out;
    for(size_t tnum = 1; tnum <= nthreads; ++tnum)
      out.emplace_back(work, nthreads, tnum, n);
    return out;
  };
  
  for(auto & x: run(nthreads)) x.join();
  
  std::cout << arr[n] << std::endl;
}


Побенчил ещё то, что мог запустить:

66,468976022 seconds time elapsed - go mt
64,819841304 seconds time elapsed - go
64,380398227 seconds time elapsed - go v3

64,001787006 seconds time elapsed - hs
62,100266396 seconds time elapsed - c++ + gcc
47,760004537 seconds time elapsed - c++ mt + gcc
51,243138067 seconds time elapsed - c++ mt + clang
80 sec - nodejs(32 бита числа)

Не получается у меня запустить код на расте — там используется левая либа. Выбывает он из битвы.

Information

Rating
Does not participate
Registered
Activity