Я пробовал скрипт bash, но создание простого файла размером 1 МБ заняло слишком много времени. Я думаю, что ответ заключается в использовании /dev/random
или /dev/urandom
, но другие посты здесь только показывают, как добавить все виды данных в файл, используя эти вещи, но я хочу добавить только цифры.
Итак, есть ли команда, которую я могу использовать для создания случайного файла размером 1 ГБ, содержащего только цифры от 0 до 9?
Изменить: я хочу, чтобы вывод был что-то вроде этого
0 1 4 7 ..... 9
8 7 5 8 ..... 8
....
....
8 7 5 3 ..... 3
Диапазон от 0 до 9, означающий только цифры 0, 1, 2, 3, 4, 5, 6, 7, 8 и 9. Также мне нужно, чтобы они были разделены пробелом и 100 на строку, до n
количества строк. Мне все равно, я хочу, чтобы мой окончательный размер был 1 ГБ.
Изменить: я использую Ubuntu 16.04 LTS
yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Ответы:
Это частично насмешливый ответ из-за названия вопроса.
Когда вы ищете «самый быстрый способ ...» , ответ почти всегда - какой-то специализированный инструмент. Это «ответы» показывает один такой инструмент, просто чтобы вы могли экспериментировать.
Это не серьезный ответ, потому что вы не должны искать специализированные инструменты для работ, которые вы выполняете только один раз или очень редко. Видите ли, в конечном итоге вы будете тратить больше времени на поиск инструментов и изучение их, чем на фактическую работу. Оболочки и утилиты, как
bash
иawk
не самые быстрые, но вы можете написать одну строчку для достижения цели, потратив всего несколько секунд.perl
Также могут быть использованы лучшие языки сценариев, такие как кривая обученияperl
, и я не решаюсь рекомендовать ее для таких целей, потому что я был травмирован ужасными Perl-проектами.python
с другой стороны, он немного затруднен из-за довольно медленного ввода-вывода; однако, это проблема, только когда вы фильтруете или генерируете гигабайты данных.В любом случае, следующая примерная программа C89 (которая использует POSIX.1 для более высокой тактовой частоты, только если она доступна) должна достичь скорости генерации около 100 МБ / с (протестирована в Linux на ноутбуке с процессором Intel i5-4200U, передавая выходные данные). к
/dev/null
), используя довольно хороший генератор псевдослучайных чисел. (Выходные данные должны пройти все тесты BigCrunch, кроме теста MatrixRank, поскольку код использует xorshift64 * и метод исключения, чтобы избежать смещения цифр.)десятичной digits.c:
Мы можем сделать это намного быстрее, если переключимся на строковый буфер и
fwrite()
один раз вместо вывода каждой цифры за раз. Обратите внимание, что мы по-прежнему сохраняем поток полностью буферизованным, чтобы избежать частичной (не степени двух) записи, если вывод является блочным устройством.Примечание: оба примера отредактированы в 2016-11-18 для обеспечения равномерного распределения цифр (ноль исключен; см., Например, здесь сравнение и подробную информацию о различных генераторах псевдослучайных чисел).
Компиляция с использованием, например,
и при желании установить общесистемный для
/usr/bin
использованияТребуется количество цифр в строке и количество строк. Поскольку
1000000000 / 100 / 2 = 5000000
(пять миллионов; общее количество байтов, разделенных на столбцы, разделенные на 2), вы можете использоватьчтобы создать размер в гигабайтах,
digits.txt
как того требует OP.Обратите внимание, что сама программа написана больше для удобства чтения, чем для эффективности. Мое намерение здесь не в том, чтобы продемонстрировать эффективность кода - я бы в любом случае использовал POSIX.1 и низкоуровневый ввод-вывод, а не в общих интерфейсах C - но чтобы вы могли легко увидеть, какой баланс существует с затраченными усилиями в разработке специализированных инструментов в сравнении с их производительностью, по сравнению с однострочниками или короткими оболочками или скриптами awk.
Используя библиотеку GNU C, вызов
fputc()
функции для каждого символьного вывода приводит к очень небольшим издержкам (косвенного вызова функции или условных выражений -FILE
интерфейс на самом деле довольно сложный и универсальный, видите ли). На этом конкретном ноутбуке Intel Core i5-4200U, перенаправляя вывод/dev/null
, первая (fputc) версия занимает около 11 секунд, тогда как линейная версия занимает всего 1,3 секунды.Я часто пишу такие программы и генераторы только потому, что мне нравится играть с огромными наборами данных. Я странный в этом смысле. Например, однажды я написал программу для печати всех конечных положительных значений с плавающей запятой IEEE-754 в текстовом файле с достаточной точностью, чтобы при синтаксическом анализе получалось точно такое же значение. Файл был размером в несколько гигабайт (возможно, 4G или около того); конечных положительных
float
значений не так много, как можно подумать. Я использовал это для сравнения реализаций, которые читают и анализируют такие данные.Для обычных случаев использования, как в случае с OP, лучше использовать сценарии оболочки и скриптлеты, а также однострочники. Меньше времени, затрачиваемого на выполнение общей задачи. (За исключением случаев, когда они нуждаются в другом файле каждый день или около того, или есть много людей, которым нужен другой файл, в котором - в редких случаях - выделенный инструмент, как указано выше, может оправдать затраченные усилия.)
источник
mmap()
это самый простой путь к лучшей скорости ввода-вывода - но это эталонный тест, прежде чем делать какие-либо заявления!write()
, обычно быстрее, чемmmap()
.fwrite()
не намного медленнее. Да, я оценил это (только не для этого конкретного примера);write()
в больших кусках (262144, 524288 или 1048576 байт) имеет тенденцию превосходить другие методы. Версия,fputc()
реализованная в библиотеке GNU C (которую я также много тестировал) является медленной по ряду причин; в частности, реализация должна выполнять условные переходы или косвенные вызовы для каждого добавленного символа; эти небольшие накладные расходы, возникающие так часто, складываются./dev/null
. Сценарий Стефана Шазеля занимает около 52 секунд; фрагмент perl (включаяhead
фильтрацию) около 58 секунд; Вашshuf
фрагмент (с правильным временем; вы измеряете только время до тех пор, пока паста больше не займет) занимает около 69 секунд. Программа C ++ 11 Джеймса Холлиса, выполняемая по очереди, занимает 14 секунд. Вышеуказанная программа занимает 10 секунд.Этот:
(при условии
head
реализации, которая поддерживает-c
) в моей системе работает достаточно быстро.tr
переводит весь диапазон байтов (от 0 до 255, от 0 до 0377 в восьмеричном): 25 первых байтов как 0, 25 следующих как 1 ... затем 25 9 остальные (от 250 до 255) в «x», который мы затем отбросить (сtr -d x
), так как мы хотим равномерного распределения (при условии, что/dev/urandom
оно само имеет равномерное распределение) и поэтому не давать смещения некоторым цифрам.Это дает одну цифру для 97% байтов
/dev/urandom
.fold -w 1
делает это одной цифрой в строке.paste -s
вызывается со списком разделителей, состоящим из 99 пробелов и одного символа новой строки, чтобы в каждой строке содержалось 100 пробелов.head -c1G
получит первый GiB (2 30 ) из этого. Обратите внимание, что последняя строка будет обрезана и не ограничена. Вы можете обрезать до 2 30 -1 и добавить недостающий символ новой строки вручную, или обрезать до 10 9 байт вместо этого, что составляет 50 миллионов из этих 200-байтовых строк (head -n 50000000
это также сделает стандартную / переносную команду).Эти временные характеристики (полученные
zsh
в четырехъядерной системе) дают представление о том, на что тратится время процессора:Первый
tr
- это горлышко бутылки, большую часть времени проводимое в ядре (я полагаю, для генерации случайных чисел). Время примерно соответствует скорости, с которой я могу получить байты/dev/uramdom
(около 19 МБ / с, и здесь мы производим 2 байта на каждый 0,97 байт / dev / urandom со скоростью 32 МБ / с).fold
кажется, тратит неоправданное количество процессорного времени (15 с) только для вставки символа новой строки после каждого байта, но это не влияет на общее время, так как оно работает на другом процессоре в моем случае (добавление-b
опции делает его немного более эффективный,dd cbs=1 conv=unblock
кажется лучшей альтернативой).Вы можете избавиться от с
head -c1G
и сбрить несколько секунд, установив ограничение на размер файла (limit filesize 1024m
сzsh
илиulimit -f "$((1024*1024))"
с большинством других оболочек ( в том числеzsh
)) , а не в субоболочке.Это можно было бы улучшить, если бы мы извлекали 2 цифры для каждого байта, но для этого нам нужен другой подход. Вышеуказанное очень эффективно, потому что
tr
просто ищет каждый байт в 256-байтовом массиве. Он не может сделать это для 2 байтов за раз, и использование подобных вещейhexdump -e '1/1 "%02u"'
вычисляет текстовое представление байта с использованием более сложных алгоритмов, было бы дороже, чем само генерирование случайных чисел. Тем не менее, если, как и в моем случае, у вас есть процессорные ядра, которые могут сэкономить время, ему все равно удастся сбрить несколько секунд:С участием:
Я получаю (заметьте, однако, что здесь это 1 000 000 000 байтов, а не 1 073 741 824):
Больше процессорного времени в целом, но лучше распределяется между моими четырьмя процессорами, так что в итоге требуется меньше настенного времени. Узкое место сейчас
hexdump
.Если мы будем использовать
dd
вместо линейногоfold
, мы можем фактически уменьшить объем работы, которуюhexdump
необходимо выполнить, и улучшить баланс работы между процессорами:(здесь предполагается GNU
dd
для егоiflag=fullblock
иstatus=none
), который дает:Вернемся к генерации случайных чисел, являющейся узким местом.
Теперь, как указал @OleTange, если у вас есть
openssl
утилита, вы можете использовать ее, чтобы получить более быстрый (особенно на процессорах с инструкциями AES) псевдослучайный генератор байтов.в моей системе выбрасывается в 15 раз больше байтов в секунду, чем
/dev/urandom
. (Я не могу комментировать, как он сравнивается с точки зрения криптографически безопасного источника случайности, если это относится к вашему варианту использования).Теперь дает:
вернуться к
hexdump
узким местом.Поскольку у меня еще есть запасные процессоры, я могу запустить 3 из них
hexdump
параллельно.(
<&3
необходим для оболочек, отличных отzsh
stdin команд закрытия, которые находятся в / dev / null при запуске в фоновом режиме).Теперь до 6,2 секунды, и мои процессоры почти полностью использованы.
источник
perl
вариант, который в любом случае был значительно медленнее. Я не могу получить 2 цифры на байт с этим подходом tr | fold | paste.bc
(затем отбросьте 0, 1 или 2 наиболее значимых цифры).Если у вас есть
shuf
доступные (последние версии GNU coreutils), вы можете сделать это:На моей виртуальной машине это сейчас немного медленнее, чем ответ Стефана, примерно в 3: 4 раза.
источник
shuf
на моей компании PC не имеет-r
,fmt
не-g
слишкомpaste
/printf
трюк - спасибо. Ваш ответ теперь, видимо, быстрее.Если вам не требуется случайность очень высокого качества, а распределение, близкое к равномерному, достаточно хорошее, вы можете работать очень быстро, особенно на современном процессоре с эффективными целочисленными векторами SIMD, такими как x86 с SSE2 или AVX2.
Это похоже на ответ @ NominalAnimal, поскольку у нас обоих была одна и та же идея, но векторизация вручную для x86. (И со случайными числами худшего качества, но все еще, вероятно, достаточно хорошими для многих сценариев использования.) Это работает примерно в 15 или 30 раз быстрее, чем код @ Nominal, при ~ 13 ГБ / с вывода ASCII на 2,5 ГГц Intel Haswell Процессор с AVX2. Это все еще меньше теоретической максимальной пропускной способности основной памяти (двухканальный DDR3-1600 составляет около 25,6 ГБ / с), но я синхронизировал запись в / dev / null, так что на самом деле он просто переписывает буфер, который остается горячим в кеше. Skylake должен выполнять этот же код значительно быстрее, чем Haswell (см. Нижнюю часть этого ответа).
Предполагая, что вы на самом деле являетесь узким местом ввода-вывода на диск или куда-то направляетесь, быстрая реализация означает, что вашему ЦП даже не нужно работать на тактовой частоте выше, чем в режиме ожидания. Он использует гораздо меньше общей энергии для получения результата. (Срок службы батареи / тепла / глобального потепления.)
Это так быстро, что вы, вероятно, не хотите записывать его на диск. Просто сгенерируйте заново по мере необходимости (из того же начального числа, если вам снова понадобятся те же данные). Даже если вы хотите передать его многопоточному процессу, который может использовать все процессоры, его запуск для передачи данных к нему оставит его горячим в кэше L3 (и кэше L2 в ядре, в котором он был записан), и очень мало процессорного времени. (Но учтите, что
/dev/null
передача по конвейеру добавляет много накладных расходов по сравнению с записью . На Skylake i7-6700k, передача по трубопроводуwc -c
или другая программа, которая просто читает + отбрасывает свой ввод, это примерно в 8 раз медленнее, чем запись/dev/null
, и использует только 70% от Процессор. Но это все еще 4,0 ГБ / с на 3,9 ГГц процессоре.Перегенерировать его быстрее, чем перечитать его даже с быстрого SSD, подключенного к PCIe, но IDK, если он более энергоэффективен (множитель векторного-целого числа остается довольно занятым, и, вероятно, довольно требователен к энергопотреблению вместе с другими AVX2). 256b векторных ALU). OTOH, я не знаю, сколько процессорного времени чтения с диска отнимает у чего-то, что максимизирует все ядра, обрабатывающие этот ввод. Я полагаю, что переключение контекста для повторной генерации в кусках по 128 КБ может быть конкурентоспособным с запуском кода файловой системы / кэша страниц и выделением страниц для чтения данных с диска. Конечно, если в страничном кэше уже жарко, то это просто memcpy. ОТО, мы уже пишем о так быстро, как memcpy! (который должен разделять пропускную способность основной памяти между чтением и записью). (Также обратите внимание, что запись в память, что
rep movsb
(оптимизированы memcpy и memset в микрокоде, что позволяет избежать RFO, поскольку Энди Глью реализовал его в P6 (Pentium Pro) )).Пока что это только подтверждение концепции, а обработка новой строки только приблизительно верна. Это неправильно на концах буфера степени 2. С большим временем разработки. Я уверен, что смог бы найти более эффективный способ вставки новых строк, который также был бы абсолютно правильным, с минимальными издержками, как это (по сравнению с выводом только пробелов). Я думаю, что это что-то вроде от 10 до 20%. Меня интересует только то, как быстро мы сможем сделать этот прогон, а не на самом деле иметь его отполированную версию, поэтому я оставлю эту часть в качестве упражнения для читателя с комментариями, описывающими некоторые идеи.
На Haswell i5 с максимальной турбо- частотой 2,5 ГГц , с оперативной памятью DDR3-1600 МГц, с тактовой частотой 100 ГБ, но с уменьшением. (Приурочен к cygwin64 на Win10 с gcc5.4
-O3 -march=native
, опущен,-funroll-loops
так как мне было достаточно трудно получить приличную синхронизацию на этом заимствованном ноутбуке. Должен был только загрузить Linux на USB).запись в / dev / null, если не указано иное.
wc -c
Cygwin с размером буфера 128 кБ: 0,32 с процессором на частоте 2,38 ГГц (макс. двухъядерный Turbo). (немасштабированное время: реальное = 32,466 с, пользователь = 11,468 с, с = 41,092 с, включая это иwc
). Правда, только половина данных была скопирована, потому что моя глупая программа предполагает, что запись выполняет полный буфер, хотя это не так, и cygwin write () делает только 64 КБ на вызов в канал.Так что с SSE2 это примерно в 15 раз быстрее, чем скалярный код @Nominal Animal. С AVX2 это примерно в 30 раз быстрее. Я не пробовал версию кода Nominal, которая просто использует
write()
вместоfwrite()
, но, по-видимому, для больших буферов stdio в основном остаётся в стороне. Если это копирование данных, это будет причиной большого замедления.Времена для производства 1 ГБ данных на Core2Duo E6600 (Merom 2,4 ГГц, 32 КБ частного L1, 4 МБ общего кэша L2), DDR2-533 МГц в 64-битном Linux 4.2 (Ubuntu 15.10). Все еще используя буфер размером 128 КБ для write (), не исследовал это измерение.
запись в / dev / null, если не указано иное.
wc -c
: 0,593 с ( немасштабированный : реальный = 59,266 с, пользователь = 20,148 с, sys = 1m6,548 с, включая время процессора в wc). Такое же количество системных вызовов write (), как и в cygwin, но на самом деле передает все данные, потому что Linux обрабатывает все 128k write () в канал.fwrite()
версии (gcc5.2-O3 -march=native
), запускаемых с./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/- 0,1%, с 1,40 инструкции за один цикл. -Funroll-петли сделали, возможно, небольшую разницу.clang-3.8 -O3 -march=native
: 3.42 с +/- 0,1%fwrite
трубопроводwc -c
: реальный = 3,980 с, пользователь = 3,176 с, sys = 2,080 с.clang++-3.8 -O3 -march=native
Линейная версия Джеймса Холлиса ( ): 22,885 с +/- 0,07%, с 0,84 инструкциями за цикл. (g ++ 5.2 был немного медленнее: 22.98 с). Писать только одну строчку за раз, наверное, очень больно.tr < /dev/urandom | ...
: реальный = 41.430s пользователь = 26.832s sys = 40.120s.tr
большую часть времени получал все ядро процессора, почти все свое время проводя в драйвере ядра, генерируя случайные байты и копируя их в канал. Другое ядро на этой двухъядерной машине работало с остальной частью конвейера.time LC_ALL=C head -c512M </dev/urandom >/dev/null
То есть, просто читая столько случайности без обвязки: real = 35.018s user = 0.036s sys = 34.940s.LANG=en_CA.UTF-8
:: real = 4m32.634s user = 4m3.288s sys = 0m29.364.LC_ALL=C LANG=C
: real = 4m18.637s user = 3m50.324s sys = 0m29.356s. Все еще очень медленно.dig3 = v%10
шаг равен примерно безубыточности на этом HW): 0,166 с (1,82 инструкции на цикл) , Это в основном нижний предел того, к чему мы можем приблизиться с совершенно эффективной обработкой новой строки.v%10
, 0,222 секунд +/- 0,4%, 2,12 инструкций за такт. (Скомпилировано с gcc5.2,-march=native -O3 -funroll-loops
циклы развертывания помогают этому коду на этом оборудовании. Не используйте его вслепую, особенно для больших программ).Как это сделано
Быстрый PRNG явно необходим. xorshift128 + можно векторизовать, поэтому у вас есть два или четыре 64-битных генератора параллельно в элементах вектора SIMD. Каждый шаг создает полный вектор случайных байтов. ( 256b реализация AVX2 здесь со встроенными Intel ). Я выбрал его из-за выбора xorshift * в Nominal, потому что 64-битное векторное целое умножение возможно только в SSE2 / AVX2 с методами расширенной точности .
Учитывая вектор случайных байтов, мы можем разделить каждый 16-битный элемент на несколько десятичных цифр. Мы производим несколько векторов 16-битных элементов, каждый из которых представляет собой ASCII-цифру + ASCII-пробел . Мы храним это непосредственно в нашем буфере вывода.
Моя оригинальная версия просто использовала
x / 6554
для получения одной случайной цифры из каждого элемента вектора uint16_t. Это всегда между 0 и 9 включительно. Это смещено9
, потому что(2^16 -1 ) / 6554
только 9,99923. (6554 = ceil ((2 ^ 16-1) / 10), что гарантирует, что частное всегда <10.)x/6554
может быть вычислено с одним умножением на «магическую» константу ( обратная фиксированная точка ) и правое смещение результата верхней половины. Это лучший случай деления на константу; некоторые делители выполняют больше операций, а подписанное деление требует дополнительной работы.x % 10
имеет аналогичное смещение и не так дешево вычислять. (Вывод asm для gcc эквивалентенx - 10*(x/10)
, то есть дополнительному умножению и вычитанию сверху деления с использованием модульного обратного умножения.) Кроме того, младший бит xorshift128 + не столь высокого качества , поэтому деление для получения энтропии из старших бит лучше ( для качества, а также скорости), чем по модулю, чтобы взять энтропию из младших разрядов.Тем не менее, мы можем использовать больше энтропии в каждом uint16_t, взглянув на младшие десятичные цифры, например, на
digit()
функцию @ Nominal . Для максимальной производительности я решил взять младшие 3 десятичных знака иx/6554
, чтобы сохранить один PMULLW и PSUBW (и, возможно, немного MOVDQA), в отличие от варианта с более высоким качеством, состоящего из 4 младших десятичных знаков. x / 6554 незначительно зависит от младших 3 десятичных цифр, поэтому существует некоторая корреляция между цифрами одного и того же элемента (8 или 16 разрядов в выходных данных ASCII, в зависимости от ширины вектора).Я думаю, что gcc делится на 100 и на 1000, а не на более длинную цепочку, которая последовательно делится на 10, так что, вероятно, это не существенно сокращает длину цепочки зависимостей, не переносимых циклами, которая выдает 4 результата от каждого выхода PRNG. port0 (векторное умножение и сдвиг) является узким местом из-за модульных мультипликативных инверсий и сдвигов в xorshift +, поэтому, безусловно, полезно сохранить умножение вектора.
xorshift + настолько быстр, что даже использование всего ~ 3,3 битов случайности из каждых 16 (т. е. 20% эффективности) не намного медленнее, чем разделение его на несколько десятичных цифр. Мы только приближаем равномерное распределение, потому что этот ответ ориентирован на скорость, пока качество не так уж плохо.
Любой вид условного поведения, в котором хранится переменное количество элементов, потребует гораздо больше работы. (Но, возможно, все еще можно сделать несколько эффективнее, используя методы левой упаковки SIMD . Однако, это становится менее эффективным для небольших размеров элементов; гигантские таблицы поиска с маской тасования нежизнеспособны, и нет никакого AVX2, пересекающего полосу, тасовавшего менее 32- битовые элементы. 128-битная версия PSHUFB все еще может генерировать маску на лету с BMI2 PEXT / PDEP, как вы можете для AVX2 с более крупными элементами , но это сложно, потому что 64-битное целое число содержит только 8 байтов. в этом ответе есть код, который может работать для большего количества элементов.)
Если задержка ГСЧ является узким местом, мы могли бы пойти еще быстрее, запустив два вектора генераторов параллельно, чередуя один из которых мы используем. Компилятор все еще может легко хранить все в регистрах в развернутом цикле, и это позволяет двум цепочкам зависимостей работать параллельно.
В текущей версии, сокращая выход PRNG, мы фактически являемся узким местом на пропускной способности порта 0, а не на задержке PRNG, так что в этом нет необходимости.
Код: версия AVX2
Полная версия с большим количеством комментариев о проводнике компилятора Godbolt .
Не очень аккуратно, извините, я должен заснуть и хочу опубликовать это.
Чтобы получить версию SSE2,
s/_mm256/_mm
,s/256/128/
,s/v16u/v8u/
, и измененияvector_size(32)
до 16. Кроме того, измените символ новой строки приращение от 4 * 16 на 4 * 8. (Как я уже сказал, код грязный и плохо настроен для компиляции двух версий. Изначально я не планировал делать версию AVX2, но потом я действительно хотел протестировать на процессоре Haswell, к которому у меня был доступ.)Компилировать с помощью gcc, clang или ICC (или, надеюсь, любого другого компилятора, который понимает диалект C GNU C от C99 и присущи Intel). Векторные расширения GNU C очень удобны для того, чтобы компилятор генерировал магические числа для деления / по модулю, используя модульные мультипликативные инверсии, и случайные
__attribute__
s полезны.Это может быть написано переносимо, но это займет больше кода.
Примечания по производительности:
Хранилище с перекрытием для вставки новых строк имеет значительные накладные расходы, чтобы решить, где его разместить (неправильные прогнозы веток и узкие места внешнего интерфейса в Core2), но само хранилище не влияет на производительность. Комментируя только эту инструкцию хранилища в ассемблере компилятора (оставляя все ветвления одинаковыми), мы практически не изменили производительность на Core2, при повторных запусках то же самое время +/- менее 1%. Таким образом, я пришел к выводу, что хранилище буфера / кеша справляется просто.
Тем не менее, использование некоторого вида вращающегося окна
ascii_digitspace
с одним элементом, имеющим символ новой строки, может быть даже быстрее, если мы развернем достаточно, чтобы исчезли какие-либо счетчики / ответвления.Запись в / dev / null в основном не работает, поэтому, вероятно, буфер остается горячим в кеше L2 (256 кБ на ядро в Haswell). Ожидается идеальное ускорение от 128b векторов до 256b векторов: никаких дополнительных инструкций нет, и все (включая магазины) происходит с удвоенной шириной. Тем не менее, ветвь вставки новой строки используется в два раза чаще. К сожалению, у меня не было времени на настройку Haswell cygwin с этой частью
#ifdef
.2,5 ГГц * 32B / 13,7 ГБ / с = 5,84 цикла на магазин AVX2 в Haswell. Это довольно хорошо, но может быть быстрее. Возможно, в системных вызовах cygwin есть некоторые издержки, чем я думал. Я не пытался комментировать их в выводе asm компилятора (что гарантировало бы, что ничего не оптимизировано).
Кэш-память L1 может поддерживать одно хранилище 32B за такт, а уровень L2 не намного ниже пропускной способности (хотя и более высокая задержка).
Когда я смотрел на IACA несколько версий назад (без разветвления для новых строк, но получая только один вектор ASCII на вектор RNG), он предсказывал что-то вроде одного хранилища векторов 32B на 4 или 5 тактов.
Я надеялся получить больше ускорения от извлечения большего количества данных из каждого результата ГСЧ, основываясь на том, как я сам смотрю на ассемблер, учитывая руководства Агнера Фога и другие ресурсы по оптимизации, ссылки на которые я добавил в вики-тэге SO x86 .)
Вероятно, это было бы значительно быстрее на Skylake , где умножение и сдвиг вектора может выполняться на вдвое большем количестве портов (p0 / p1) по сравнению с Haswell (только p0). xorshift и извлечение цифр используют много сдвигов и умножений. ( Обновление: Skylake использует 3,02 IPC, что дает нам 3,77 цикла на 32-байтовое хранилище AVX2 , рассчитанное на 0,030 с на 1 ГБ итерации, запись в
/dev/null
Linux 4.15 на i7-6700k с частотой 3,9 ГГц.Для нормальной работы не требуется 64-битный режим . Версия SSE2 так же быстра при компиляции
-m32
, потому что ей не нужно очень много векторных регистров, а вся 64-битная математика выполняется в векторах, а не в регистрах общего назначения.На самом деле это немного быстрее в 32-битном режиме на Core2, потому что слияние / ветвление макросов работает только в 32-битном режиме, поэтому меньше ошибок в ядре из строя (18,3 с (1,85 инструкций за такт) против 16,9 с (2,0 МПК)). Меньший размер кода из-за отсутствия префиксов REX также помогает декодерам Core2.
Кроме того, некоторые перемещения векторов reg-reg заменяются нагрузками, поскольку не все константы больше фиксируются в векторных регистрах. Поскольку пропускная способность из кэша L1 не является узким местом, это действительно помогает. (например, умножение на постоянный вектор
set1(10)
:movdqa xmm0, xmm10
/pmullw xmm0, xmm1
превращается вmovdqa xmm0, [constant]
/pmullw xmm0, xmm1
.) Поскольку для reg-reg MOVDQA требуется порт ALU, он конкурирует с реальной выполняемой работой, но загрузка MOVDQA конкурирует только за полосу пропускания декодирования внешнего интерфейса. (Наличие 4-байтового адреса во многих инструкциях сводит на нет большую выгоду от сохранения префиксов REX.Я не удивлюсь, если реальный выигрыш получит сохранение ALU MOVDQA в мопах, поскольку внешний интерфейс должен очень хорошо отставать от среднего значения 2,0 IPC.
Все эти различия исчезают в Haswell, где все это должно запускаться из кэша decoded-uop, если не из буфера обратной петли. ALU + ветвление макро-синтеза работает в обоих режимах начиная с Nehalem.
источник
Вот решение, которое, я надеюсь, простое для понимания:
od
создает равномерный поток шестнадцатеричных цифр из/dev/random
.tr
избавляется от букв, только сохраняя0-9
цифрыfold
обеспечивает 100 цифр в строкеawk
вставляет пробелы внутри строкhead
обрезает ввод до 1 гигабайтаисточник
Вы можете использовать
jot
команду для этого:источник
fmt
не имеет опции ширины ворот. В любом случае, это будет точно, потому что все цифры занимают ровно один столбец!fmt
версияfmt (GNU coreutils) 8.25
(Ubuntu 16.04)536870912
Это похоже на метод Стефана Шазела, но я читаю 64 бита за раз, чтобы улучшить производительность. Распределение по-прежнему равномерно, но теперь вы получаете 19 цифр на каждые 8 байтов вместо только 8 в лучшем случае, как и раньше
На 32-битной платформе каждый раз будет читаться 9 цифр вместо 19.
источник
perl
не скомпилирована с поддержкой четырех типов.next if $n >= 1000000000; $s = sprintf("%09u", $n);
чтобы она получала только 9 цифр$n = unpack("Q")
если quad не поддерживается.BEGIN{$/=\4; $,=" "} $n = unpack("L");
также<16e18
и делите его на 16, вы получите 18 цифр 86,7% при 1,95 дБ. С 32-битным,<4e9 /4
получает 9 цифр 93,1% для 2,10 дБ. Но 5 байтов (в виде гексагона (H10))<1e12
дают 12 цифр 90,9% для 2,18 дпБ, или разделение гекса на половину и выполнение каждой половины<1e6
дает 6 цифр 95,4% для 2,29 дпБ; это приближается к пределу log_10 (256) = 2,41.Я согласен с Nominal Animal в использовании скомпилированного языка программирования, если вам нужна скорость. Тем не менее, вам не нужно писать собственный код RNG на C. C ++ 11 предлагает отличную версию Mersenne Twister в составе стандартной библиотеки.
Приведенный выше код достаточно прост и занимает около минуты, когда я передаю вывод в файл. Мы можем пойти намного быстрее, создав строку, достаточно большую для 100 цифр, и взломав ее. Это позволяет нам вызывать cout каждую строку, а не каждую цифру.
Этот код занимает у моей машины около шести секунд. Помните, что это стандартный вывод, так что направьте его в файл.
У меня есть пара заявлений об отказе. Во-первых, я пишу это на ПК с Windows. Я думаю, что все библиотеки присутствуют в Linux, но если я ошибаюсь, обязательно укажите это.
Кроме того, он фактически выводит ровно полмиллиарда цифр, разделенных пробелами, что является технически гигабайтом, но, возможно, не совсем тем, что вы хотели. Он выводит 5 миллионов строк по 100 цифр в строке. Если разница важна, вы можете увеличить количество строк. В моем окне Windows файл кажется немного больше, чем 10 ^ 9 байт, что я думаю, что-то делать с дополнительными символами новой строки.
источник
/dev/null
который был бы намного быстрее, чем запись в реальный файлwrite()
системном вызове - это memcpy в pagecache, который блокируется, только если ядро решит сделать это вместо того, чтобы выделять больше буферного пространства. Эта программа должна быть узким местом на дисковых операциях ввода / вывода, когда не хватает памяти или если вы использовали O_DIRECT для обхода кэша страниц. Если выwrite()
занимаетесь кусками меньше размера кеша, надеюсь, ваши данные попадают в основную память только один раз, а перезаписываемый буфер остается горячим в кэш-памяти L2 или L3.Это зависит от вашего определения «случайный». Если вы имеете в виду криптографически случайный, вам просто нужно взять хорошую библиотеку и откусить пулю, дождитесь ее запуска.
Если вам просто нужно что-то, что выглядит довольно случайно, вот простой способ:
На медленной машине может потребоваться час; достаточно быстро и достаточно случайно для большинства целей.
источник
/dev/urandom
скорее всего будет лучшеgzip
, как по скорости, так и по случайности.Get a file that is several Gb long
вам понадобится файл ** по крайней мере 8 ГБ, чтобы получить файл размером 1 ГБисточник
cat file | tr
когда вы можете простоtr <file
. IIRC, ты можешь даже<file tr
. Я думал, что вы только что говорили об этом сценарии оболочки, который выглядел неуклюжим и медленным, какdu | awk
после каждой строки, чтобы проверить размер, и заново открывал файл для добавления каждой строки вместо перенаправления вне цикла.cat /dev/urandom | busy-cmd
- один из тех редких случаев, когда он может иметь смысл, поскольку он может разделить случайную генерацию и занятый cmd между процессорами. Не так много для tr, но имеет значение для Сэма,od
например.