2017-01-11 2 views
2

Я получаю некоторые CSV-файлы от клиента. Средний размер этих CSV составляет 20 МБ.Можно ли хранить огромные данные в структуре данных Perl

Формат:

Cutomer1,Product1,cat1,many,other,info 
Cutomer1,Product2,cat1,many,other,info 
Cutomer1,Product2,cat2,many,other,info 
Cutomer1,Product3,cat1,many,other,info 
Cutomer1,Product3,cat7,many,other,info 
Cutomer2,Product5,cat1,many,other,info 
Cutomer2,Product5,cat1,many,other,info 
Cutomer2,Product5,cat4,many,other,info 
Cutomer3,Product7,cat,many,other,info 

Мой текущий подход: хранить все эти записи временно в таблице, а затем запросить в таблице:

where customer='customer1' and product='product1' 
where customer='customer1' and product='product2' 
where customer='customer2' and product='product1' 

Проблема: вставки в БД а затем выбор занимает слишком много времени. Многое происходит, и для обработки одного CSV требуется 10-12 минут. В настоящее время я использую SQLite, и это довольно быстро. Но я думаю, что я сэкономлю еще немного, если полностью удалю вставку и выбор.

Мне было интересно, нормально ли хранить этот полный CSV в некоторой сложной структуре данных perl?

У машины обычно есть 500 МБ + свободная оперативная память.

+2

Сколько запросов вам нужно сделать? Использование SQLite - это хороший подход, потому что вы также можете сделать это для вас. Создание собственного индекса в структуре данных Perl легко, если это всегда один и тот же запрос, но вам нужно больше памяти, если имеется более одного индекса. Но если вы не индексируете, ваши поиски будут медленными. DBI имеет драйвер CSV, но это может быть и медленным. – simbabque

+0

10 кв запросов в CSV. Многое происходит, и для обработки одного CSV требуется 10-12 минут. В настоящее время я использую SQLite, и это довольно быстро. Но я думаю, что я сэкономлю еще немного, если полностью удалю вставку и выбор. Никогда не слышал о «индексе в структуре данных Perl». Не могли бы вы дать ссылку? – GrSrv

+2

Вы меня неправильно поняли. То, что вам нужно, называется индексом _search_. Я хотел сказать, что вам нужно создать свой собственный такой индекс поиска и поместить его внутри структур данных Perl. См. Мой ответ. Но самый важный вопрос: ** Сколько различных запросов вы делаете? **. – simbabque

ответ

2

Если запрос, который вы показываете, является единственным запросом, который вы хотите выполнить, это довольно прямолинейно.

my $orders; # I guess 
while (my $row = <DATA>) { 
    chomp $row; 
    my @fields = split /,/, $row; 

    push @{ $orders->{$fields[0]}->{$fields[1]} } \@fields; # or as a hashref, but that's larger 
} 

print join "\n", @{ $orders->{Cutomer1}->{Product1}->[0] }; # typo in cuStomer 

__DATA__ 
Cutomer1,Product1,cat1,many,other,info 
Cutomer1,Product2,cat1,many,other,info 
Cutomer1,Product2,cat2,many,other,info 
Cutomer1,Product3,cat1,many,other,info 
Cutomer1,Product3,cat7,many,other,info 
Cutomer2,Product5,cat1,many,other,info 
Cutomer2,Product5,cat1,many,other,info 
Cutomer2,Product5,cat4,many,other,info 
Cutomer3,Product7,cat,many,other,info 

Вы просто создаете индекс в хеш-ссылке, имеющей несколько уровней. На первом уровне есть клиент. Он содержит еще один hashref, который имеет список строк, соответствующих этому индексу. Тогда вы можете решить, хотите ли вы всего лишь массива ref, или если вы хотите разместить хэш-код с ключами там. Я пошел с массивом ref, потому что это потребляет меньше памяти.

Позже вы можете запросить его легко. Я включил это выше. Вот результат.

Cutomer1 
Product1 
cat1 
many 
other 
info 

Если вы не хотите, чтобы помнить индексы, но есть код много различных запросов, вы могли бы сделать переменные (или даже константы), которые представляют magic numbers.

use constant { 
    CUSTOMER => 0, 
    PRODUCT => 1, 
    CATEGORY => 2, 
    MANY  => 3, 
    OTHER => 4, 
    INFO  => 5, 
}; 

# build $orders ... 

my $res = $orders->{Cutomer1}->{Product2}->[0]; 

print "Category: " . $res->[CATEGORY]; 

Выход:

Category: cat2 

Чтобы заказать результат, вы можете использовать в Perl sort. Если вам нужно сортировать по двум столбцам, есть ответы на SO, которые объясняют, как это сделать.

for my $res ( 
    sort { $a->[OTHER] cmp $b->[OTHER] } 
    @{ $orders->{Customer2}->{Product1} } 
) { 
    # do stuff with $res ... 
} 

Однако, вы можете найти только клиента и продукта, как это.

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


Мне было интересно, если это нормально, чтобы сохранить эту полную CSV в какой-то сложной структуры данных в Perl?

Для этой конкретной цели, абсолютно. 20 мегабайт не много.

Я создал тестовый файл, который составляет 20004881 байт и 447848 строк с этим кодом, что не идеально, но выполняет свою работу.

use strict; 
use warnings; 
use feature 'say'; 
use File::stat; 

open my $fh, '>', 'test.csv' or die $!; 
while (stat('test.csv')->size < 20_000_000) { 
    my $customer = 'Customer' . int rand 10_000; 
    my $product = 'Product' . int rand 500; 
    my $category = 'cat' . int rand 7; 
    say $fh join ',', $customer, $product, $category, qw(many other info); 
} 

Вот отрывок из файла:

$ head -n 20 test.csv 
Customer2339,Product176,cat0,many,other,info 
Customer2611,Product330,cat2,many,other,info 
Customer1346,Product422,cat4,many,other,info 
Customer1586,Product109,cat5,many,other,info 
Customer1891,Product96,cat5,many,other,info 
Customer5338,Product34,cat6,many,other,info 
Customer4325,Product467,cat6,many,other,info 
Customer4192,Product239,cat0,many,other,info 
Customer6179,Product373,cat2,many,other,info 
Customer5180,Product302,cat3,many,other,info 
Customer8613,Product218,cat1,many,other,info 
Customer5196,Product71,cat5,many,other,info 
Customer1663,Product393,cat4,many,other,info 
Customer6578,Product336,cat0,many,other,info 
Customer7616,Product136,cat4,many,other,info 
Customer8804,Product279,cat5,many,other,info 
Customer5731,Product339,cat6,many,other,info 
Customer6865,Product317,cat2,many,other,info 
Customer3278,Product137,cat5,many,other,info 
Customer582,Product263,cat6,many,other,info 

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

use strict; 
use warnings; 
use Devel::Size 'total_size'; 

use constant { 
    CUSTOMER => 0, 
    PRODUCT => 1, 
    CATEGORY => 2, 
    MANY  => 3, 
    OTHER => 4, 
    INFO  => 5, 
}; 

open my $fh, '<', 'test.csv' or die $!; 

my $orders; 
while (my $row = <$fh>) { 
    chomp $row; 
    my @fields = split /,/, $row; 

    $orders->{ $fields[0] }->{ $fields[1] } = \@fields; 
} 

say 'total size of $orders: ' . total_size($orders); 

Здесь:

total size of $orders: 185470864 

Так что переменная потребляет 185 мегабайт. Это намного больше, чем 20 МБ CSV, но у нас есть легкодоступный для поиска индекс. Используя htop, я понял, что фактический процесс занимает 287 МБ. У моей машины 16 ГБ памяти, поэтому мне все равно. И примерно с 3,6 с достаточно быстро запустить эту программу, но у меня SSD новая машина CORE i7.

Но он не будет есть всю вашу память, если у вас есть 500 МБ, чтобы сэкономить. Вероятно, подход SQLite будет потреблять меньше памяти, но вам нужно сравнить скорость этого и подход SQLite, чтобы решить, какой из них более эффективен.

Я использовал метод described in this answer, чтобы прочитать файл в базе данных SQLite . Сначала мне нужно было добавить строку заголовка в файл, но это тривиально.

$ sqlite3 test.db 
SQLite version 3.11.0 2016-02-15 17:29:24 
Enter ".help" for usage hints. 
sqlite> .mode csv test 
sqlite> .import test.csv test 

Поскольку я не мог измерить это правильно, скажем, это чувствовал около 2 секунд. Затем я добавил индекс для конкретного запроса.

sqlite> CREATE INDEX foo ON test (customer, product); 

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

sqlite> SELECT * FROM test WHERE customer='Customer23' AND product='Product1'; 
Customer23,Product1,cat2,many,other,info 

Результат появился мгновенно (что не является научным!). Поскольку мы не измеряли, как долго извлекается структура данных Perl, мы не можем сравнивать их, но похоже, что все это занимает примерно одно и то же время.

Однако размер файла SQLite составляет всего 38839296, что составляет около 39 МБ. Это больше, чем файл CSV, но не много. Похоже, что процесс sqlite3 потребляет около 30 КБ памяти, что я считаю странным с учетом индекса.

В заключение SQLite кажется немного более удобным и потребляет меньше памяти. В Perl нет ничего плохого, и это может быть такая же скорость, но использование SQL для этого типа запросов кажется более естественным, поэтому я бы пошел на это.

Если бы я мог быть таким смелым, я бы предположил, что вы не указали индекс на своем столе, когда вы сделали это в SQLite, и это заставило его заняться больше времени. Количество строк у нас здесь не так уж и много, даже для SQLite. Правильно проиндексирован это кусок пирога.

Если вы действительно не знаете, что делает индекс, подумайте о телефонной книге.Он имеет индекс первых букв по бокам страниц. Чтобы найти Джона Доу, вы схватите D, а потом как-то посмотрите. Теперь представьте, что такого не было. Вам нужно в случайном порядке выталкивать еще много. А потом попробуйте найти парня с номером телефона 123-555-1234. Это то, что делает ваша база данных, если нет индекса.


1) Если вы хотите, чтобы сценарий этого, вы можете также трубы или читать команды в утилиту sqlite3 создать базу данных, а затем использовать DBI Perl, чтобы сделать обработку запросов. В качестве примера, sqlite3 foo.db <<<'.tables\ .tables' (где обратная косая черта \ представляет собой литературный рубеж) печатает список таблиц дважды, поэтому импорт такой же будет работать.

+0

Эй, я люблю ваш ответ, но мой главный вопрос не отвечает: * Мне было интересно, нормально ли хранить этот полный CSV в некоторой сложной структуре данных perl? *. Я никогда не создавал структуру данных своего размера. – GrSrv

+0

@GrSrv oh. Правда, я не ответил на это. Хорошо, пока ваша машина не падает, это нормально делать все, что вы хотите. Вопрос, который вы должны задать, - это полезно сделать это. ;) Я добавлю немного больше ответа. – simbabque

+0

@GrSrv Я закончил обновление ответа. Есть ориентир, некоторые размышляют о жизнеспособности подхода и сравнении с SQLite. Повеселись. :) – simbabque

Смежные вопросы