LINUX.ORG.RU

Релиз shuf-t 1.2 — аналог shuf для файлов размером с RAM

 ,


2

12

Хочу представить публике утилиту shuf-t — аналог shuf (случайная перестановка строк файла) из GNU Core Utilities, предназначенный для работы с текстовыми файлами, размеры которых сравнимы или превышают доступную RAM.

Факты:

  • Shuf-t был написан с нуля на Qt5. Версия 1.2 перенесена на boost 1.5.8, без Qt. Но QtCreator \ qmake все еще используются для сборки.
  • Распространяется под лицензией Simplefied BSD.
  • Командный интерфейс совместим с shuf.
  • На данный момент доступен только deb пакет. Windows версия будет собрана позднее.
  • Для тасования строк использован алгоритм Фишера-Йетса

Кому мал shuf?

Основная сфера применения shuf-t — задачи машинного обучения. Конкретнее - подготовка данных для Vowpal Wabbit. Машинное обучение позволяет классифицировать (и не только) результаты на основе исторической выборки. Яркий пример — банковский скоринг (давать/не давать кредит). Чем больше результатов кредитования увидит классификатор — тем он точнее (обычно).

Столкнувшись с данными, объемом превосходящими RAM одного компьютера, экспериментатор вынужден обращаться к кластерам (MapReduce и т.п.) и облакам. Либо воспользоваться системами online machine learning, лучшей из которых сейчас является Vowpal Wabbit. Принцип VW — получил пример, обработал его и забыл. В памяти хранятся только коэффициенты результирующих уравнений, которые подгоняются по получаемым примерам. Т.о. VW может работать на одном ПК с наборами данных неограниченного размера.

У подобных систем есть минусы, и один из них — чувствительность к порядку предъявляемых для обучения классификатора данных. К примеру, если вы отсортируете ваш набор кредиторов по результату, и предъявите VW сначала 10000 неплательщиков, а затем 10000 исправно погасивших долг, то после обучения классификатор будет всех заносить в последнюю группу, т.к. о неплательщиках он давно «забыл». Выходом в данном случае является равномерное размазывание записей о плохих и хороших заемщиках по набору данных. Что и достигается рандомизацией порядка строк в текстовом файле размером в сотню гигабайт.

Как работает?

Очень просто - в RAM считываются не строки файла, а их адреса в нем. Их порядок рандомизируется. В соответствие с новым порядком строк исходный файл считывается в выделенный в RAM буфер и сохраняется, куда пожелал пользователь. Нюансом тут является то, что файл считывается в несколько проходов (т.к. буфер меньше его размера), а offset'ы строк сортируются так, что чтение всегда идет от начала файла к его концу. Т.е. не выполняется seek() адреса меньше текущей позиции чтения.

Скорость работы зависит от числа строк, их размера и меняется нелинейно. 4Гб файл с дефолтным буфером в 1Гб у меня обрабатывается 4 минуты.

>>> Проект на github



Проверено: Shaman007 ()
Последнее исправление: Truf (всего исправлений: 3)
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: ЛОМАЮЩИЕ НОВОСТИ от Lincor

обнаружен эталон говноеда.

Говноедство это не использовать Qt.

В Qt решены многие проблемы, которые пришлось бы героически решать без Qt и тащить ворох низкокачественных либ.

Обосную:

Как сто лет уже Qt это не только гуйцы. QtCore отвязан от графики и от иксов.

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

* парсинг командной строки, с выдачей хелпа (аналог boost::program_options)

* правильная и удобная работа с юникодом

* интернационализация

* все объекты шарят свое тело при копировании а это плюс к быстродействию

* регэкспы

* работа с сетью, в том числе http клиент со всякими там SSL, проксями и прочими ништяками

* своя реализация shared_ptr для старых компиляторов

* система логгирования

* юнит тесты

* плагины

* thread pool

* платформонезависимая сериализация, которая не ломается от релиза к релизу в отличие от boost::serialization

* парсинг XML, JSON

Собственно всё это приправлено еще качественной документацией с примерами.

Вот поэтому в случае не использования Qt мы занимаемся говноедством обмазываясь всякими дурно пахнущими либами.

Boost, конечно, тоже хорош и много чего позволяет, но не всё там гладко.

navrocky ★★
()
Ответ на: комментарий от navrocky

Вот поэтому в случае не использования Qt мы занимаемся говноедством обмазываясь всякими дурно пахнущими либами.

А пользуясь - жрём Qt и кресты.

devl547 ★★★★★
()
Ответ на: комментарий от TheAnonymous

Она разве перестала тормозить?

EXL ★★★★★
()
Ответ на: комментарий от Truf

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

бугага!!!

сейчас посчитаю, сколько будет этим заниматься нормально написанный sort -R (гнутый сорт все-таки похоже тормоз)

итак, 1Г строк, каждая, скажем, по 500 байт? допустим даже не рейд-массив, а обычный диск с со скоростью 170 МБ/с

допустим 4Г оперативки на компе, т.е. режем на файлы по 2ГБ за раз

1. прочитать 2ГБ и посчитать хэши его строк — это 12 с
2. отсортировать — это примерно 4М строк, значит 22 прохода по памяти, которой, скажем, 32*4М = 128М — это вообще меньше секунды
3. записать 2ГБ обратно — это опять 12 с

итак 250 рандомно отсортированных файлов это чуть меньше чем 125 минут, т.е 2 часа

теперь делаем (сортирующее) слияние 250 файлов — это опять по времени только прочитать и записать 500 ГБ, само слияние займет ничтожно мало — получаем опять 2 часа, даже меньше (около 3000 секунд)

итого: 4 часа на все про все, при этом именно сортировка будет занимать от силы 20 минут (хэши и то, скорее всего, дольше считаться будут)

почему это куда быстрее, чем у тебя? потому что делается всего лишь 2 (прописью: два) прохода по диску на чтение и столько же на запись, а твоя утилита изъездит диск в незнамо сколько проходов

опять же плюс этого решения в том, что скорость от снижения количества оперативки на компе почти не упадет

э-хе-хех

согласен... эхехех, пейсатели...

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 2)
Ответ на: комментарий от www_linux_org_ru

посчитать хэши его строк
отсортировать

А в чём тут сакральный смысл, если нам наоборот нужно шаффлить?

devl547 ★★★★★
()
Ответ на: комментарий от Truf

Вообще, вопрос алгоритма «попроще» поднимался на kaggle, т.к. у каждого питонщика оказался свой костыль для тасовки больших файлов. Меня более-менее устроила как раз эта эвристика.

ыыы!!!

у вас там че, *никто* не знает нормального алгоритма тасовки больших файлов, и эвристиками пользуются?!

На какие куски вы будете резать такой файл и сколько проходов с тасованием вам понадобиться, чтобы гарантировать то, что он хорошо перемешан?

произвольные, но желательно равные

ровно 1 проход

мне кажется (но доказывать лень), что мой вариант дает несмещенные перестановки, т.е. все перестановки равновероятны

лень доказывать еще и потому, что для sort -R это доказывать не надо — но если ты покажешь, что перестановки м.б. не равновероятны, то это будет интересно

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от devl547

А в чём тут сакральный смысл, если нам наоборот нужно шаффлить?

сортировка хэшей в порядке возрастания дает шаффл — это, по-моему, очевидно

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

сортировка хэшей в порядке возрастания дает шаффл

Если хэш достаточно качественный, то да. Ладно, это мысль вслух была.

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

devl547 ★★★★★
()
Последнее исправление: devl547 (всего исправлений: 2)
Ответ на: комментарий от www_linux_org_ru

Не дает. У тебя все одинаковые строки будут иметь один хеш и соберутся пучком. Хеши нужно шаффлить. И сливать файлы беря по строке из рандомного файла.

Truf
() автор топика
Ответ на: комментарий от devl547

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

так добавь в начало строки хотя бы ее номер в файле, и сразу все строки станут уникальными

но вообще исходные строки, по идее, должны уже иметь первичный ключ, и значит быть уникальными

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от navrocky

Говноедство это не использовать Qt.

У вас дурчанка! Убери Хауса ибо ты не адекватен

anonymous
()
Ответ на: комментарий от www_linux_org_ru

Ну ок. Допустим есть файл размером N. И число B (буфер). Что быстрее? Пройтись по одному файлу (N/B)+1 раз и записать N байт один раз. Я замечу, что когда я говорю пройтись, я имею в виду вызов seek() для отсортированных по возрастанию offset'ов строк, наполняющих текущий буфер. Т.е. я (N/B) раз считываю B байт из этого файла. Итого всего read - 2*N байт, write - N байт. У меня из этих 2*N один N считывается в (N/B) проходов с вызовом seek.

Или считать исходный файл размером в N 1 раз. Расписать его N байт по (N/B) файлам. Затем считать по B байт из каждого из (N/B) файлов, рандомно между ними скача. И записать N байт в файл-результат. Итого read 2*N, write 2*N. И один из read 2*N делается из (N/B) файлов. И один из 2*N write делается в (N/B) файлов.

Я бы вот посмотрел... devl547 хотел сделать что-то простое и быстрое. Если у него не выходит с mmap, то может он реализует этот вариант и мы померимся перформансом. Если ваш способ лучше, я его с удовольствием реализую в версии v1.3. вместо текущего.

Truf
() автор топика
Ответ на: комментарий от Truf

в версии v1.3

Не-не-не, просто расскажи тем кому ты рекламировал свою программу, про более лучшую программу! Нефиг велосипеды на куте писать

anonymous
()
Ответ на: комментарий от Truf

Итого всего read - 2*N байт, write - N байт.

нет

ты думаешь, будто читаешь из файла только те строки, которые тебе нужны, но ОС (и диск) так не умеют — они читают страницами по 4 кб скажем, а ненужное тебе ОС или libc просто *выбрасывает*, но время это все равно занимает

т.е. реально у тебя читается с диска около N+(N/B)N = N²/B+N байт, что ты мягко назвал «меняется нелинейно», гы-гы (правда кэширование диска это смягчает)

а с учетом того, что seek диска на далекие расстояния существенно больше seek-а на рядом лежащую дорожку, можно условно, для расчета скорости, считать, что считывание с диска идет страницами по 1МБ или даже по 2МБ

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 3)
Ответ на: комментарий от Truf

devl547 хотел сделать что-то простое и быстрое. Если у него не выходит с mmap

У меня сейчас чуток не выходит по времени, я теперь папка(

Я для начала хотел поиграться с пайпами и стандартным shuf, не внося туда вообще никаких изменений.

devl547 ★★★★★
()
Ответ на: комментарий от Truf

Расписать его N байт по (N/B) файлам.

Затем считать по B байт из каждого из (N/B) файлов, рандомно между ними скача.

не так

затем считать приблизительно по B/(N/B) байт из каждого из N/B файлов.

рандомной скачки не будет, если хватит размера буферов

т.е если B/(N/B) будет не менее 20МБ, то это составит 10 «суперстраниц» диска, т.е. для целей скорости можно считать что это не рандомное чтение

если же размера буферов не хватит, то да, будет подтормаживание — если B/(N/B) будет 2МБ, то торможение м.б. 50%, однако тут может быть и больше (из-за дорожек диска), и меньше (из-за использования elevator algorithm ОС или даже самому... но это лень)

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от Truf

Если у него не выходит с mmap

и вряд ли выйдет — тут надо алгоритм хороший, а не сервис со стороны ОС

update: хотя elevator algorithm со стороны OS возможно бы пригодился

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от Truf

Буфер 1Гб (дефолтный) - 21.37 мин; Буфер 2Гб - 13.55 мин

это шаффл 10-гигового файла на ssd?

по моим предположениям он должен занимать максимум (10/0.5)*4 = 80 секунд — хотя, впрочем, тут уже будут очень влиять тормоза от подсчета хэшей

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от Truf

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

не-не, там он какой-то другой алгоритм предлагает, и явно ограниченный:

but assume that we do not intertwin the various decks

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от Truf

Просто чтобы считать его нужно 120 сек.

чувак, ты меня потрясаешь

я покупал можно сказать самый дешевый (128 ГБ емнип) ссд для бука, так он реально мне выдавал скорость чтения 500 МБ/с, я проверял!

/me сидит и чешет репу, размышляя, что в таких условиях, наверно, если написать Enterprise Super Shuffle Ultimate Gold Edition и продавать его исключительно в коробках, с usb ключём, и активацией серийного номера голосом по телефону, то спрос будет достаточный

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 2)
Ответ на: комментарий от www_linux_org_ru

да, похоже ограниченный

Truf
() автор топика
Ответ на: комментарий от Truf

Просто чтобы считать его нужно 120 сек.

щас вот смахнул пыль с бука и сделал wc -l на группе файлов в 20+ ГБ, выполнилось за 108 секунд — т.е. на 10Гб было бы 54 секунды

и это надо назвать жуткими тормозами, и виновата в этом наверно фс

а вот на raw партиции в 10 ГБ выдало скорость чтения 400 МБ/с — видимо все же 400 у него, а не 500 (давно покупал, пару лет назад)

так что ладно, корректирую цифру: (10/0.4)*4 = 100 секунд на шаффл 10ГБ файла

вот только md5 на моем недобуке со скоростью 400 МБ/с наверно не получится, у меня получилось около 200 МБ/с на md5

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Ммм. Вот такой пример:

Допустим в файле 3 строки: {1,2,3} После тасования вероятность строки 1 оказаться на последнем месте - 1/3.

Пилим оригинальный файл на 2 буфера и оба тасуем. Получаем либо [{1}, {2,3}], либо [{1}, {3,2}].

При составлении файла-результата путем взятия первой строки из рандомного файла вероятность стоки 1 оказаться на последнем месте 1/4. Верно же?

Truf
() автор топика
Ответ на: комментарий от Truf

При составлении файла-результата путем взятия первой строки из рандомного файла вероятность стоки 1 оказаться на последнем месте 1/4. Верно же?

очевидно же *, что брать надо с вероятностью, пропорциональной количеству оставшихся элементов в файле т.е. из первого файла 1/3, из второго 2/3

т.е. с вероятностью 1/3 получим 1 на первом месте, с вероятностью 1/3 получим 2 на первом месте, с вероятностью 1/3 получим 3 на первом месте

______________________________

* очевидно хотя бы потому, что один из файлов может оказаться пустым, и из него надо брать с вероятностью 0

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 1)
Ответ на: комментарий от www_linux_org_ru

Я что-то туплю уже на ночь глядя. Не вижу где вероятность потерял. Вот такой сеттинг:

5 строк {12345}. 4 ушло в один файл, 1 в другой. Какова вероятность строки 1 занять 3-ю позицию? В норме 1/5.

З-ю позицию она может занять либо из такого расклада: [{x,1,x,x}, {x}], либо из такого [{x,x,1,x} {x}]. Вероятность обоих этих случаев по 1/4. Теперь считаем вероятность занятия 3-й позиции в первом случае: 1/4*(1/5+4/5*1/4) = 1/4*(1/5+1/5) = 2/20

вероятность занятия 3-й позиции во втором случае: 1/4*(4/5*3/4) = 1/4(3/5) = 3/20

Итого 5/20 = 1/4 А должно быть 1/5 Где я затупил?

Truf
() автор топика
Ответ на: комментарий от www_linux_org_ru

очевидно же *

«Первая строка на последнем месте» - неудачный пример. Лучше «первая строка по-прежнему на первом месте».

При случайной перестановке, как уже писали, вероятность 1/3. Теперь делим на 2 группы: [{1}, {2,3}] или [{1}, {3,2}]. В обоих случаях 1 будет по-прежнему на месте с вероятность 1/2 (то есть в том случае, если первая выборка будет из меньшего множества).

omegatype ★★★
()
Ответ на: комментарий от omegatype

Да, но тянуть эту 1 он предлагает в первый раз с вероятностью 1/3, а потом с 1/2. Итого 1/6. Две случая дадут 2/6=1/3

Truf
() автор топика

Подписался на тред.

Xintrea ★★★★★
()

Нафига вообще что-то делить на файлы? Задача рандомизировать а не сортировать. Делается все за два прохода, сначала читаем исходный файл, определяем смещения начала строк и их длины, на втором проходе случайно считываем и записываем в выходной файл. Все. И хеши тут нафиг не нужны они и время съедят не мерянно и место в памяти.

anonymous
()

случайный seek будет в любом случае либо во входном файле (явный или не явный при mmap), либо в выходном (явный или не явный). чтобы вы тут не придумали. так что два прохода как писалось выше.

anonymous
()

Можете конечнотс хешами, сначала они тупо не влезут в память, потом вынесете их в отдельный файл который будете сортировать, при этом случайный seek никуда не уйдет)))

anonymous
()

На mmap

Ну нате на mmap. ftp://ftp.simtreas.ru/pub/my/shuf-f.c никакого буфера нет, чисто mmap. Память юзается под массивы смещений и масив перпестановок номеров. Никакого Qt, а всего каких-то 300 строк. Сделайте генератор тестового файла для сравнения - будем сравнивать скорости :)

vodz ★★★★★
()
Ответ на: комментарий от Truf

Как бы помягче сказать... Ваша ссылка - вселенская глупость. Разве вас я просил дать мне гибабайты мусора?

vodz ★★★★★
()
Ответ на: комментарий от vodz

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

Pinkbyte ★★★★★
()
Ответ на: комментарий от Pinkbyte

Вот потому у генератора (а это примитивная программа) должны быть опции: делать файл из одного символа в строке, из гигабайтов в одной строке (моя то прожуёт, да, ей пофиг, а вот у оригинального автора это указанное (но только тут) ограничение), или из случайной длины каждой строки. rand/srand в glibc - стандартен, не надо обмениваться мусором из /dev/random.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Вот потому у генератора (а это примитивная программа) должны быть опции: делать файл из одного символа в строке, из гигабайтов в одной строке

плюсану

шаффлу пофиг (ну почти пофиг) на данные внутри строки, ему важны только длины строк, поэтому давайте сделаем генератор

кроме того, мне на том инете что сейчас у меня, нереально скачать несколько гигов

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от Pinkbyte

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

если хочется соостветствия с этими данными, то пусть кто-нить, кому не лень, скачает этот файл, применит к нему

perl -Wne 'print length()."\n"' > lengths.txt
 — это даст длины строк, архивнет эти данные (zp/zpaq очень прилично жмет), и выложит куда-то

дальше генератор может писать любой бред, но соблюдая полученные длины строк

т.е. простейший генератор будет

cat lengths.txt | perl -Wne '$_ = "a" x ($_-1); print "$_\n";'

www_linux_org_ru ★★★★★
()
Последнее исправление: www_linux_org_ru (всего исправлений: 2)

Нормальный человек бы сложил строки в lmdb, по записи на строку. Зачем мучиться с текстовыми файлами?

anonymous
()
Ответ на: комментарий от www_linux_org_ru

шаффлу пофиг (ну почти пофиг) на данные внутри строки, ему важны только длины строк, поэтому давайте сделаем генератор

Шаффлу на длин строк тоже пофиг. А уж тем более на данные. Ему на вход надо массив [1 2 3 ...] на выходе - случайно переставленный он же. Ничего другого, кроме одной ячейки памяти размером с типа массива под swap temporary variable.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Господа, я не против генераторов нисколько. Просто, во-первых, я не хочу такой генератор делать (я ж его на Qt напишу, вы ж понимаете). Во-вторых, я уходил по делам.

Хотите - сгенерируйте 10.5Gb файл. Если интересно, в нем 45840617 строк. Возьмите средний размер. Надо - вышлю длины, как предложил www_linux_org_ru. А хотите - скачайте оригинал. Я бы на вашем месте скачал оригинал и не парился.

Truf
() автор топика
Ответ на: комментарий от vodz

Шаффлу на длин строк тоже пофиг.

совсем не пофиг

если ты будешь рандомно прыгать по достаточно большому файлу на жестком диске, то скорость чтения у тебя будет крайне низкой — я даже скажу сколько, примерно 100 *строк* в секунду

соответственно, в случаях А. строки у тебя по 2 байта, и В. строки по 2000 байт размер файла будет разный; и в случае А размер файла может оказаться достаточно малым, чтобы с помощью кэширования OS на нем нормально работал сколь угодно глупый алгоритм шаффла

ТС кстати это понимает

www_linux_org_ru ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.