Why doesn't the process stop when I exit the shell?
Возможно, потому что вы сказали
disown
оболочкеdisown
. Или вы можете не использовать интерактивную оболочку управления заданиями в качестве процесса лидера сеанса.Комбинированная модель групп сеансов и процессов, на которой мир остановился в 1980-х годах, работает следующим образом. :Существует лидер сеанса процесс. Это то, что получает сигнал зависания от терминальной линейной дисциплины , когда терминал кладет трубку. Ожидается, что он будет усиливать этот сигнал, посылая сигнал всем «заданиям», которые он породил. Это сделают интерактивные оболочки управления заданиями ; большинство других программ не предполагают, что они будут выполнять обязанности руководителей сессий, и не будут.
Использование
nohup
приводит к тому, что процессы в этих «заданиях» игнорируют сигнал зависания, когда лидер сеанса посылает им его. Использованиеdisown
заставляет оболочку лидера сеанса забыть о задании, так что она вообще никогда не отправляет ему сигнал о зависании.Can I safely assume my processes won't end if I don't use nohup?
Нет. Если они не были
disown
изменены, то им будет отправлен сигнал завершения сеанса интерактивным процессом оболочки управления заданиями.Более того, :Люди из systemd пытались реализовать механизм пользовательского -пространства, эквивалентный сеансам ядра, но они сделали из этого немного свиного уха, что вносит дополнительные сложности в (некоторые )] системные операционные системы. При зависании сеанса systemd вместо отправки
SIGHUP
только лидеру сеанса, как это делает линейная дисциплина ядра, они ошибочно отправляютSIGTERM
на все процессы, ошибочно означая завершение работы , а не зависание .
SIGTERM
ко всем процессам — это, конечно, то, что происходит в стандартной модели групп сеансов и процессов при завершении работы системы; и это убивает все процессы,в том числе отвергнутые и те, которые игнорируют сигнал зависания. Конечно, это также уничтожит ваши процессы, которые не были ниdisown
ed, ниnohup
ed.Дополнительная литература
#!/usr/bin/perl
use strict;
use Set::Tiny;
my $max = shift;
# A set to hold all unique data_points:
my $all_dps = Set::Tiny->new();
# hash of sets. key is id, val is the set of data_points for that id:
my %ids = ();
# hash containing the number of data_points for each id:
my %counts = ();
# read the input file, parse into %ids
while(<>) {
chomp;
my ($id,$count,$dp) = split /\s+/,$_,3; #/
$ids{$id} = Set::Tiny->new(split /\s*,\s*/, $dp); #/
# The "#/" commentS exists solely to fix U&Ls perl syntax highlighting
$counts{$id} = $count;
$all_dps = $all_dps->union($ids{$id});
};
my $total_dps = keys %{ $all_dps };
# array to hold the list of output ids:
my @idlist=();
# set to hold the output data points:
my $data_points = Set::Tiny->new();
# count of output data points:
my $dpcount=0;
# stop when id list is = max. or when the count of output data points is equal
# to he total data points. or when there are no non-empty keys left.
while ((@idlist < $max) && ($dpcount < $total_dps) && (keys %ids > 0)) {
# sort the counts hash by value.
my @counts = ( sort { $counts{$b} <=> $counts{$a} } keys %counts );
# add the sets from the id with the highest count to the output set.
$data_points = $data_points->union($ids{$counts[0]});
# and add that id to the output id list
push @idlist, $counts[0];
$dpcount = keys %{ $data_points };
foreach (keys %ids) {
my $diff = $ids{$_}->difference($data_points);
if (keys %{$diff} == 0) {
# delete ids with no difference from the current data_points set.
delete $ids{$_};
delete $counts{$_};
} else {
# add the intersection count as a decimal fraction so ids with more
# dupes sort slightly higher.
my $intersection = $ids{$_}->intersection2($data_points);
$counts{$_} = (keys %{$diff}). ".". (keys %{$intersection});
};
};
};
print join(",",@idlist). "\t$dpcount/$total_dps\t".
join(",",(sort keys %{ $data_points })). "\n";
Этот скрипт сначала считывает весь входной файл и использует модуль Perl Set ::Tiny для построения «набора» (, т. е. хэша perl )и хэша, содержащего количество множества элементов для каждого идентификатора. Set::Tiny
доступен по ссылке CPAN выше, или он может быть уже упакован для вашего дистрибутива (, например. на Debian:sudo apt-get install libset-tiny-perl
).
Затем он неоднократно пытается построить максимально большой выходной набор,:
%counts
хэша по значению По сути, это алгоритм, который вы назвали «неуклюжим» в своем комментарии. Я предпочитаю думать об этом как о «прямой -вперед» или «грубой -силе» :-)
.Я попробовал несколько разных способов оптимизации, но не смог найти более эффективного. Это не обязательно означает, что его нет. Это просто означает, что я не мог найти его. Основная трудность заключается в необходимости приоритизации идентификаторов с более избыточными точками данных.
В любом случае, у меня нет входного файла с миллионами записей, поэтому я не смог провести временные тесты. Мне было бы интересно узнать, как быстро он работает с полным набором данных. а также насколько хорошо он работает, если вы используете MLDBM или аналогичный, как указано ниже.
Этот скрипт будет использовать много оперативной памяти. Если у вас 12 миллионов идентификаторов, он будет использовать примерно 12 MB * (the average id string length + the average data points length per id)
. Это может быть проблемой, если у вас менее 32 ГБ или, возможно, даже 64 ГБ оперативной памяти.
Если сценарий действительно превышает доступную вам оперативную память и вызывает перегрузку подкачки, вы можете использовать модуль MLDBM или один из модулей Tie::
для хранения хэша %ids (и, возможно, % тоже считает хэш )в базе данных, а не в памяти. например. Свяжите ::DBI для использования базы данных sqlite, mysql или postgresql и т. д.
Использование MLDBM
или модуля Tie::
может не быть быстрее (, хотя и может, потому что он не будет перегружать ОЗУ и выполнять подкачку ), но )скрипт с гораздо меньшей вероятностью умереть до завершения из-за нехватки -из -состояния памяти,и b )это будет гораздо менее вредно для других процессов, запущенных в вашей системе (, которые в противном случае могут быть уничтожены из-за нехватки памяти ).
напр. добавьте следующее сразу после строк my %ids=()
и my %counts=()
, чтобы использовать файл Berkeley DB для %ids:
use MLDBM qw(DB_File Storable);
use Fcntl;
my $id_db = tie %ids, 'MLDBM', './ids.db', O_CREAT|O_RDWR, 0640 or die $!;
и, возможно, это тоже, чтобы связать хэш %counts с базой данных DB:
my $count_db = tie %counts, 'MLDBM', './counts.db', O_CREAT|O_RDWR, 0640 or die $!;
Пример вывода:
Я сохранил этот скрипт как ryan.pl
, сделал его исполняемым с chmod +x ryan.pl
и запустил как:
$./ryan.pl 1 input.txt
id1 5/8 E1,E2,E3,E4,E5
$./ryan.pl 2 input.txt
id1,id3 7/8 E1,E2,E3,E4,E5,E6,E7
$./ryan.pl 3 input.txt
id1,id3,id5 8/8 E1,E2,E3,E4,E5,E6,E7,E8
Здесь на U&L плохо видно, но вывод разделен табуляцией -.
Некоторое предварительное тестирование (с входным файлом размером 145 МБ с 1 миллионом строк, каждая из которых содержит от 1 до 20 случайных словарных слов в качестве точек данных _), показало, что мое первоначальное предположение об использовании памяти было совершенно неверным.
Загрузка наборов в ОЗУ заняла около 23 минут (, и это только для загрузки файла данных без его обработки ), и потребляла 1 ГБ ОЗУ на моем Phenom II 1090T (с установленным 32 ГБ ОЗУ, но только около 8 ГБ. бесплатно ).
При использовании MLDBM загрузка файла данных заняла около 21 минуты. Он создал файл ids.db
размером 323 МБ и файл counts.db
размером 78 МБ. При этом он использовал постоянные 9,3 МБ ОЗУ.
Итак, я предполагаю, что размер вашего файла данных как минимум в 10 -20 раз больше, поэтому вряд ли он поместится в ОЗУ. Используйте MLDBM, желательно на твердотельном накопителе NVME для лучшей производительности.
Поскольку вы просили, вот обновленная версия скрипта. Может быть, вы сможете извлечь из него какие-то полезные идеи.
Это как минимум в два раза быстрее, чем предыдущая версия. Потребовалось всего 15 минут, чтобы не только прочитать мой тестовый файл размером 145 МБ, но и обработать его и получить результат для 12 идентификаторов -. Лучшее, что я мог получить с другими попытками оптимизации, составило около 33 минут.
Это, ИМО,по-прежнему совершенно не подходит для чрезвычайно больших наборов данных, таких как упомянутый вами файл размером 104 ГБ.
Если вы все же хотите попробовать, я бы порекомендовал разделить его на два скрипта. Один для заполнения файлов.db (все до и включая цикл while (<>)
), а второй скрипт (все до этого цикла while (<>)
, но без операторов unlink
, конечно, а затем почти все после него )для работы с КОПИЕЙ файлов.db.
Это связано с тем, что по крайней мере половина времени выполнения -приходится на чтение текстового файла и его сохранение в файлах.db. Для нескольких запусков будет гораздо быстрее просто скопировать файлы.db и обработать копию, чем создавать их с нуля при каждом запуске.
(копии необходимы, поскольку сценарий изменяет и удаляет записи в хэшах %ids и %counts по мере обработки данных. работа с копией позволяет быстро вернуть файлы.db к начальным условиям)
#!/usr/bin/perl
use strict;
use Set::Tiny;
# The first arg is the maximum number of identifiers we want.
# Any remaining args (and stdin) are input.
my $max = shift;
# hash of sets. key is id, val is the set of data_points for that id:
my %ids = ();
# hash containing the number of data_points for each id:
my %counts = ();
# The following two arrays exist to minimise memory usage, so that datapoints
# which appear in multiple IDs are stored in %id by reference rather than
# value.
#
# Array containing each datapoint indexed numerically
my @dp=();
# Hash containing each datapoint indexed by value
my %seen=();
use BerkeleyDB ;
use MLDBM qw(BerkeleyDB::Btree Storable);
# delete the.db files
unlink './ids.db';
unlink './counts.db';
unlink './seen.db';
unlink './dp.db';
# use MLDBM for the %ids hash - we need to serialise the Set::Tiny
# data structures.
tie %ids, 'MLDBM', -Filename => 'ids.db', -Flags => DB_CREATE or die "Cannot open database 'ids.db': $!\n";
# It's faster to use BerkeleyDB directly for the other arrays (they
# contain scalar values, so there is no need for serialisation)
tie %counts, 'BerkeleyDB::Btree', -Filename => 'counts.db', -Flags => DB_CREATE or die "Cannot open database 'counts.db': $!\n";
tie %seen, 'BerkeleyDB::Btree', -Filename => 'seen.db', -Flags => DB_CREATE or die "Cannot open database 'seen.db': $!\n";
tie @dp, 'BerkeleyDB::Recno', -Filename => 'dp.db', -Flags => DB_CREATE or die "Cannot open database 'dp.db': $!\n";
my $total_dps=0;
# read the input file, parse into %ids
while(<>) {
chomp;
# split input line on spaces with max of 3 fields.
my ($id,$count,$data) = split(/\s+/,$_,3); #/
# split $data by commas
my @data = split(/\s*,\s*/, $data); #/
my $data_count = @data;
my @data_by_idx = ();
# convert @data to @data_by_idx
for (0..$#data) {
if (!defined($seen{$data[$_]})) {
# We haven't seen this datapoint before, so add it to both @dp
# and %seen.
$dp[++$total_dps] = $data[$_];
$seen{$data[$_]}=$total_dps;
};
# add the datapoint's index number to @data_by_idx
push @data_by_idx, $seen{$data[$_]};
};
$ids{$id} = Set::Tiny->new(@data_by_idx);
$counts{$id} = $count;
};
# array to hold the list of output ids:
my @idlist=();
# set to hold the output data points:
my $data_points = Set::Tiny->new();
# count of output data points:
my $dpcount=0;
my $biggest_id='unknown';
my $biggest_count=0;
# stop when id list is = max. or when the count of output data points
# is equal to he total data points. or when there are no non-empty
# keys left.
while ((@idlist < $max) && ($dpcount < $total_dps) && (keys %ids > 0)) {
# find the id with the most data points without using sort().
if ($biggest_id eq 'unknown') {
foreach (keys %counts) {
if ($counts{$_} > $biggest_count) {
$biggest_count = $counts{$_};
$biggest_id = $_;
};
};
};
# add the sets from the id with the highest count to the output set.
$data_points = $data_points->union($ids{$biggest_id});
# and add that id to the output id list
push @idlist, $biggest_id;
$dpcount = keys %{ $data_points };
$biggest_count=0;
foreach (keys %ids) {
my $diff = $ids{$_}->difference($data_points);
if (keys %{$diff} == 0) {
# delete ids with no difference from the current data_points set.
delete $ids{$_};
delete $counts{$_};
} else {
# add the intersection count as a decimal fraction so ids with more
# dupes sort slightly higher.
my $intersection = $ids{$_}->intersection2($data_points);
$counts{$_} = (keys %{$diff}). ".". (keys %{$intersection});
# find the new id with the most data points.
if ($counts{$_} > $biggest_count) {
$biggest_count = $counts{$_};
$biggest_id = $_;
};
};
};
};
print join(",",@idlist). "\t$dpcount/$total_dps\t";
print join (",", (map $dp[$_], keys %{ $data_points })), "\n";
Что касается вашего другого вопроса в комментарии (, то есть как разделить данные для многоядерной -обработки в кластере ), я понятия не имею.
Я не думаю, что это задача, поддающаяся осколкам данных, параллельной обработке осколков и последующему объединению результатов, потому что НАСТОЯТЕЛЬНО любому такому процессу потребуется доступ ко всему всему набору данных для создания любого осмысленный вывод.
Эта задача связана с вводом-выводом -, а не с процессором -в любом случае -это не сложно или «дорого» в вычислительном отношении, просто требуется много времени (и памяти )для читать и обрабатывать огромный набор данных.
Не верь мне на слово, мысль. Я почти ничего не знаю о ваших данных или о том, что вы пытаетесь сделать.Кто-то, кто a )лучше понимает ваш набор данных и b )знает, что вы пытаетесь извлечь из него, может эффективно сегментировать ваши данные и по-прежнему иметь возможность комбинировать наборы результатов.