Задача состоит в том, чтобы быстро отфильтровать большой файл.
- Входные данные: в каждой строке три положительных числа, разделенных пробелом
Выход: все входные строки
A
B
,T
удовлетворяющие любому из следующих критериев.- Там существует еще один линейный вход
C
,D
,U
гдеD = A
и0 <= T - U < 100
. - Там существует еще один линейный вход
C
,D
,U
гдеB = C
и0 <= U - T < 100
.
- Там существует еще один линейный вход
Для создания тестового файла используйте следующий скрипт Python, который также будет использоваться для тестирования. Это сделает файл 1.3G. Конечно, вы можете уменьшить nolines для тестирования.
import random
nolines = 50000000 # 50 million
for i in xrange(nolines):
print random.randint(0,nolines-1), random.randint(0,nolines-1), random.randint(0,nolines-1)
Правила. Самый быстрый код при тестировании входного файла, который я создаю с помощью вышеуказанного скрипта на моем компьютере, выигрывает. Крайний срок - одна неделя с момента первого правильного ввода.
Моя машина Время будет запущено на моей машине. Это стандартная установка Ubuntu 8 ГБ ОЗУ на восьмиъядерный процессор AMD FX-8350. Это также означает, что мне нужно иметь возможность запустить ваш код.
Некоторая важная информация о времени
Времена обновлены для запуска следующего перед каждым тестом.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time wc test.file
real 0m26.835s
user 0m18.363s
sys 0m0.495s
time sort -n largefile.file > /dev/null
real 1m32.344s
user 2m9.530s
sys 0m6.543s
Статус записей
Я запускаю следующую строку перед каждым тестом.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
- Perl (Ожидание исправления ошибки.)
- Скала 1 минута 37 секунд от @James_pic. (Использование scala -J-Xmx6g Filterer largefile.file output.txt)
- Java . 1 минута 23 секунды @Geobits. (Использование java -Xmx6g Filter_26643)
- C . 2 минуты 21 секунда @ScottLeadley.
- C . 28 секунд @James_pic.
- Питон + панды . Может быть, есть простое «групповое» решение?
- C . 28 секунд @KeithRandall.
Победителями стали Кит Рэндалл и James_pic.
Я не мог отличить их время бега, и оба почти так же быстры, как и туалет!
1 < n < 2147483647
?Ответы:
C, ~
74,1 секундыRadix сортируйте по T, затем проходите по массиву в поисках совпадений.
Это быстро, потому что это кеш дружественный. Основание вроде разумно, и последняя прогулка очень. Я должен проверить каждую строку примерно на 100 других, но все они находятся рядом в кэше.
Добавлено: мне больше не нужно проверять каждую строку на предмет сканирования 100 других строк. Небольшой таблицы подсчетов младших битов b в окне достаточно, чтобы исключить большинство случаев этого сканирования.
Теперь примерно 1/2 разбор, 1/3 раз сортировка, 1/6 время фактического сопоставления.
источник
filter.c
чтобы делать то же самое, пришел к вопросу и нашел это. +1Скала 2.10 - 0:41
Проблема в основном:
Большинство РСУБД заметят, что соединение
x.a
кy.b
имеет самую высокую специфичность, и планировать это как хэш - соединение.Вот что мы будем делать. Мы создаем хеш-
a
таблицу данных , хеш-код соединяем их с той же таблицейb
и фильтруем разницуt
.Компилировать с:
И запустить с:
На моей машине это работает за 2 минуты 27.
Тем не менее, может быть интересно попробовать подход из ответа @ Lembik, но на более быстром языке. Это соответствует чему-то вроде объединения слиянием на
t
. На бумаге это должно быть медленнее, но имеет лучшую локальность кэша, что может продвинуть его вперед.Обновить
Мне удалось сэкономить большую часть времени с удивительно небольшими изменениями - лучшим хэш-минглером. Хеш-карта очень чувствительна к скоплению хешей, поэтому на моей машине это изменение приводит к 1:45.
Большая часть этого времени уходит на чтение данных в массив.
Мне любопытно, почему мой код чтения данных намного медленнее, чем @Geobits. Мой код занимает 70 секунд, чтобы прочитать данные - дольше, чем вся программа @Geobits, как только
Thread.start
ошибка устранена. У меня возникает соблазн украсть подход @Geobits к чтению данных, но я не уверен, что боги Stack Exchange будут относиться к этому.Обновление 2
Я сделал дальнейшие улучшения, на этот раз для чтения данных. Использование сопоставления с образцом и операций монады внутри цикла снижало производительность, поэтому я упростил его. Я думаю, что
scala.io.Source
это следующее узкое место для решения.Сейчас на моей машине до 1:26.
Обновление 3
Избавившись от
probe
от OpenHashMultiMap. Код теперь более java-ish и работает в 1:15.Обновление 4
Я сейчас использую FSM для разбора ввода. Время выполнения до 0:41
источник
StringTokenizer
, но когда я делаю, я анализирую миллионы строк.String.split
в настоящее время узкое место, ноStringTokenizer
не намного лучше сейчас - распределение в тесном внутреннем цикле вредит моему и без того напряженному GC. Я работаю над ФСМ, который, кажется, обещает (хотя и является излишним)Ява: 1 м 54 с
(На моем i7)
Так как каждое совпадение будет в пределах 100
t
от его помощника, я решил объединить входыt
. Для каждых 100 есть ведро, поэтому, чтобы проверить число, нужно сравнить только с +/- 1 ведром.В среднем, каждая корзина содержит только 100 записей, поэтому не требуется много времени для сканирования нескольких корзин для каждой. Более половины времени тратится на чтение и ведение, сопоставление занимает всего около 40 секунд.
Примечание. В зависимости от настроек JVM может потребоваться увеличить размер кучи. Это также предполагает имя файла
test.file
. Просто измените его в строке 24, если это не так.источник
Thread::run
, нетThread.start
, так что все работает вmain
потоке. СThread::start
, время выполнения падает от 1:38 до 0:46 на моей машине.sort
время. Я поднял кучу до 6G, так же, как у меня (вы сказали, что у вас было 8G, так что это казалось разумным предположением).C - 12 секунд
Я решил перенести свой ответ Scala на C, чтобы увидеть, насколько большую производительность я смог получить.
Это более или менее тот же подход (создать открытую хэш-таблицу на
a
), за исключением того, что я пропускаю шаг, на котором строю исходный массив, и выполняю итерации непосредственно из хеш-таблицы (по какой-то причине я никогда не мог заставить этот подход выполнить в Scala - Я подозреваю, что виновата встраивание JVM).Я не беспокоился о нитях, потому что это было трудно делать переносимо.
Код является:
Компилировать с:
И запустить с:
Расположение тестового файла жестко закодировано как «test.file».
Еще раз, чтение данных занимает большую часть времени (чуть менее 9 секунд). Соответствие занимает все остальное время.
Опять же, будет интересно посмотреть, как это противоречит ответу Скотта Лидли, в котором используется тот же язык, но другая стратегия. Скотт присоединяется к T, что в принципе означает, что у него будет больше возможностей присоединиться, но опять же, присоединение к T дает лучшую локальность кэша.
источник
diff <(sort -n James_pic-c.out) <(sort -n James_pic-scala.out)
a
значение встречается,n
когдаn >= BUFFER_SIZE + 2
Perl, 17m46s на ядре i7 с 8 Гб памяти
Во-первых, мы используем sort -n -k3 для упорядочения наиболее важных полей, используя преимущества встроенного параллелизма в современных версиях sort (1). Затем, поскольку perl сильно мешает тот факт, что простой скаляр занимает порядка 80 байтов каждый (50 миллионов * 3 * 80 - это слишком много - по крайней мере, 12 ГБ), мы сокращаем вывод до 50 миллионов * 12 байтов. массив (12 байт на строку, каждая строка содержит 3 целых числа, которые можно представить в виде 32-разрядного целого числа). Затем мы запускаем 8 потоков, покрывающих (примерно) 1/8 данных (+ некоторое перекрытие).
Несортированный вывод:
Я уверен, что это будет на порядок быстрее в C, но я, вероятно, не буду тратить время на это.
источник
A = D = 8455767
, ноU = 50175
,T = 50130
и такT - U = -45
C # - 30 секунд
Подход, отличный от большинства, если я читаю правильно - я не использую хеш-структуры.
Я не получаю результатов, не уверен, является ли это статистической аномалией или ошибкой в моих рассуждениях.Исправлено, сравнение для двоичной сортировки было некорректным.источник
x.A
будет происходитьsortedA
иx.B
будет происходитьsortedB
, тогда как на самом деле оба будут происходитьsortedB
, и этоComparer
приведет к бессмысленные результаты.A
иB
, есть более быстрый алгоритм, чем итерация поA
и двоичный поиск поB
которому естьO(n log(n))
(и, по сути, это хеш-таблица бедного человека). Вместо этого вы можете объединить два списка, который естьO(n)
.B
будут равномерно распределены в определенном диапазоне, заключается в замене двоичного поиска на интерполяционный поиск, что сокращает время поиска сO(log(n))
доO(log(log(n))
.С
Жестокая, грубая сила, уродливая C. На поприще я бы выбрал любой другой скомпилированный язык.
Скомпилируйте с помощью "gcc -m64 -pthreads -O". Ожидает ввода на стандартный ввод. Запускает многопоточность по умолчанию. Используйте опцию "-s", чтобы использовать только один поток.
источник
Наконец-то у меня появилась возможность создать физическую систему Ubuntu 14.04, похожую на систему Лембика, и сделать посмертное решение по этой головоломке. В моем выборе важности:
неудачным,были неэффективными:Вместо того, чтобы утомлять вас еще одним анализатором FSM, в приведенном ниже решении используются функции fgets () и локальная замена strtol () [ищите s2i ()].
Ссылочная реализация в Ruby:
Это собака, примерно в 50 раз медленнее, чем решение C, но Perl такой же медленный и менее лаконичный.
Решение C:
Скомпилируйте с помощью "gcc -O3 -std = c99 -Wall -m64".
источник