Примечание Первая часть имеет подход с использованием файла кэша, вторая - подход с sqlite
, а затем проводится сравнение между ними.
Будет ли какое-то одно решение достаточно быстрым для этой цели, конечно, полностью зависит от всех этих чисел. Так же как и лучший подход.
Для того, что вы показываете - крошечные файлы, из которых очень мало изменений - основы должны быть достаточно хороши
use warnings;
use strict;
use feature 'say';
my $fcache = 'cache.txt'; # format: filename,epoch,processed_string
open my $fh, '<', $fcache or die "Can't open $fcache: $!";
my %cache = map { chomp; my @f = split /,/, $_, 3; shift @f => \@f } <$fh>; #/
close $fh;
for (@ARGV) {
my $mtime = (stat)[9];
# Have to process the file (and update its record)
if ( $cache{$_}->[0] < $mtime ) {
@{$cache{$_}} = ($mtime, proc_file($_));
}
say $cache{$_}->[1];
}
# Update the cache file
open my $fh_out, '>', $fcache or die "Can't open $fcache: $!";
say $fh_out join(',', $_, @{$cache{$_}}) for keys %cache;
sub proc_file { # token processing: join words with _
my $content = do { local (@ARGV, $/) = $_[0]; <> };
return join '_', split ' ', $content;
}
Примечания
Это не сохранит порядок записей в кэше, так как используется ха sh, что, похоже, не имеет значения. Если это необходимо, вам нужно знать (записать) существующий порядок строк, а затем отсортировать их так, прежде чем писать
Выбор точной структуры файла «кеша» и используемой в программе структуры данных несколько произвольны, как образцы. Улучшите это, во что бы то ни стало
Для работы вышеупомянутого уже должен существовать файл кэша в формате, указанном в комментарии: filename,seconds-since-epoch,string
. Добавьте код, чтобы написать его, если он не существует
Самым крупным потребителем здесь является строка, заполняющая сложную структуру данных из файла размером в 50 КБ. Это должно оставаться самой трудоемкой частью, пока файлы малы, и только немногие нуждаются в обработке
Я бы сказал, что использование sqlite
в большинстве случаев добавит накладные расходы для такой небольшой проблемы. ,
Если число файлов, обрабатываемых каждый раз, выходит за рамки небольшого количества, то вы можете попробовать его параллельно - учитывая, как мало они составляют большую часть времени, затрачиваемого на накладные расходы при доступе к файлам, и, возможно, есть там достаточно "локтевой комнаты", чтобы выиграть от параллельной обработки. Кроме того, в общем случае ввод / вывод, безусловно, может быть ускорен при параллельной работе, но это полностью зависит от обстоятельств.
Я подумал, что это идеальный случай для сравнения с sqlite
, поскольку я ' Я не уверен, чего ожидать.
Во-первых, я записываю 50000 крошечных файлов (a N b
) в отдельный каталог (dir
)
perl -wE'for (1..50_000) { open $fh, ">dir/f$_.txt"; say $fh "a $_ b" }'
(всегда используйте три аргумента open
нормально!) На моем старом ноутбуке это заняло 3 секунды.
Теперь нам нужно создать файл кэша и базу данных (sqlite
) с этими файлами, затем обновить их и сравнить обработка с использованием программ с sqlite
и с файлом кэша.
Вот первый код для подхода с использованием sqlite
.
Создание и заполнение базы данных в файле files.db
use warnings;
use strict;
use feature 'say';
use DBI;
my ($dir, $db) = ('dir', 'files.db');
my $dbh = DBI->connect("DBI:SQLite:dbname=$db", '', '', { RaiseError => 1 });
my $table = 'files';
my $qry = qq( create table $table (
fname text not null unique,
mtime integer not null,
string text
); );
my $rv = $dbh->do($qry);
chdir $dir or die "Can't chdir to $dir: $!";
my @fnames = glob "*.txt";
# My sqlite doesn't accept much past 500 rows in single insert (?)
# The "string" that each file is digested into: join words with _
my $tot_inserted = 0;
while (my @part = splice @fnames, 0, 500) {
my @vals;
for my $fname ( @part ) {
my $str = join '_',
split ' ', do { local (@ARGV, $/) = $fname; <> };
push @vals, "('$fname'," . (stat $fname)[9] . ",'$str')";
}
my $qry = qq(insert into $table (fname,mtime,string) values )
. join ',', @vals;
$tot_inserted += $dbh->do($qry);
}
say "Inserted $tot_inserted rows";
Это заняло около 13 секунд, одноразовые расходы. Я insert
500 строк за раз, так как мой sqlite
не позволит мне сделать намного больше; Я не знаю, почему это так (я поместил PostgreSQL
в несколько миллионов строк в одном операторе вставки). При наличии ограничения unique
для столбца он индексируется .
Теперь мы можем изменить несколько временных отметок
touch dir/f[1-9]11.txt
, а затем запустить программу для обновления sqlite
база данных для этих изменений
use warnings;
use strict;
use feature 'say';
use DBI;
use Cwd qw();
use Time::HiRes qw(gettimeofday tv_interval);
my $time_beg = [gettimeofday];
my ($dir, $db) = ('dir', 'files.db');
die "No database $db found\n" if not -f $db;
my $dbh = DBI->connect("DBI:SQLite:dbname=$db", '', '', { RaiseError => 1 });
# Get all filenames with their timestamps (seconds since epoch)
my $orig_dir = Cwd::cwd;
chdir $dir or die "Can't chdir to $dir: $!";
my %file_ts = map { $_ => (stat)[9] } glob "*.txt";
# Get all records from the database and extract those with old timestamps
my $table = 'files';
my $qry = qq(select fname,mtime,string from $table);
my $rows = $dbh->selectall_arrayref($qry);
my @new_rows = grep { $_->[1] < $file_ts{$_->[0]} } @$rows;
say "Got ", 0+@$rows, " records, ", 0+@new_rows, " with new timestamps";
# Reprocess the updated files and update the record
foreach my $row (@new_rows) {
@$row[1,2] = ( $file_ts{$row->[0]}, proc_file($row->[0]) );
}
printf "Runtime so far: %.2f seconds\n", tv_interval($time_beg); #--> 0.34
my $tot_updated = 0;
$qry = qq(update $table set mtime=?,string=? where fname=?);
my $sth = $dbh->prepare($qry);
foreach my $row (@new_rows) {
$tot_updated += $sth->execute($sth);
}
say "Updated $tot_updated rows";
$dbh->disconnect;
printf "Runtime: %.2f seconds\n", tv_interval($time_beg); #--> 1.54
sub proc_file {
return join '_',
split ' ', do { local (@ARGV, $/) = $_[0]; <> };
}
Это явно не печатается. Я оставил это, поскольку есть несколько способов сделать это, хотя я не был уверен, что именно нужно печатать. Я бы, вероятно, запустил еще один select
для этой цели, после того как все будет обновлено.
Программа занимает удивительно стабильно около 1,35 секунды в среднем за несколько прогонов. Но до той части, где он update
- база данных для этих (немногих!) Изменений, это занимает около 0,35 секунды, и я не понимаю, почему update
из нескольких записей занимает столько времени по сравнению.
Далее, чтобы сравнить, нам нужно завершить подход , используя файл кэша из первой части этой позиции, записав этот файл кэша (то, что там осталось). Полная программа очень немного отличается от первой
use warnings;
use strict;
use feature 'say';
use Cwd qw();
my ($dir, $cache) = ('dir', 'cache.txt');
if (not -f $cache) {
open my $fh, '>', $cache or die "Can't open $cache: $!";
chdir $dir or die "Can't chdir to $dir: $!";
my @fnames = glob "*.txt";
for my $fname (@fnames) {
say $fh join ',', $fname, (stat $fname)[9],
join '_', split ' ', do { local (@ARGV, $/) = $fname; <> };
}
say "Wrote cache file $cache, exiting.";
exit;
}
open my $fh, '<', $cache or die "Can't open $cache $!";
my %fname = map { chomp; my @f = split /,/,$_,3; shift @f => \@f } <$fh>; #/
my $orig_dir = Cwd::cwd;
chdir $dir or die "Can't chdir to $dir: $!";
my @fnames = glob "*.txt";
for my $f (@fnames) {
my $mtime = (stat $f)[9];
# Have to process the file (and update its record)
if ( $fname{$f}->[0] < $mtime ) {
@{$fname{$f}} = ($mtime, proc_file($f));
say "Processed $f, updated with: @{$fname{$f}}";
}
#say $fname{$_}->[1]; # 50k files! suppressed for feasible testing
}
# Update the cache
chdir $orig_dir or die "Can't chdir to $orig_dir: $!";
open my $fh_out, '>', $cache or die "Can't open $cache: $!";
say $fh_out join(',', $_, @{$fname{$_}}) for keys %fname;
sub proc_file {
return join '_',
split ' ', do { local (@ARGV, $/) = $_[0]; <> };
}
Написание кеша изначально занимает около 1 секунды. После того, как несколько файлов touch
-ed, как в тесте sqlite
, следующий запуск этой программы, опять же довольно последовательно, занимает около 0,45 секунды.
С этими тестами я должен сделать вывод, что подход sqlite
немного медленнее для этих условий. Тем не менее, это, безусловно, гораздо более масштабируемо, в то время как проекты только имеют тенденцию расти в размере. Напомним также, что update
базы данных занимает совсем немного (относительно), что меня удивляет; возможно, что-то не так с моим кодом, и возможно ускорить это.