Низкая производительность чтения на Python

Следуя предыдущей теме, я свалил свою проблему на ее косые кости, перейдя с Perl-скрипта на Python, я обнаружил огромную проблему с производительностью с отсечением файлов в Python. Выполнение этого на сервере Ubuntu.

NB: это не поток X или Y, который мне нужно знать в корне, если это так, или если я делаю что-то глупое.

Я создал свои тестовые данные, 50 000 10kb файлов (это отражает размер файла avg того, что я обрабатываю):

mkdir 1 cd 1 for i in {1..50000}; do dd if=/dev/zero of=$i.xml bs=1 count=10000; done cd .. cp -r 1 2 

Создал мои 2 скрипта как можно проще:

Perl

 foreach my $file (<$ARGV[0]/*.xml>){ my $fh; open($fh, "< $file"); my $contents = do { local $/; <$fh> }; close($fh); } 

питон

 import glob, sys for file in glob.iglob(sys.argv[1] + '/*.xml'): with open(file) as x: f = x.read() 

Затем я очистил кеши и запустил 2 сценария slurp, между каждым запуском я снова очистил кеши, используя:

 sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 

Затем отслеживалось, чтобы каждый раз читать все с диска:

 sudo iotop -a -u me 

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

 $ time python readFiles.py 1 real 5m2.493s user 0m1.783s sys 0m5.013s $ time perl readFiles.pl 2 real 0m13.059s user 0m1.690s sys 0m2.471s $ time perl readFiles.pl 2 real 0m13.313s user 0m1.670s sys 0m2.579s $ time python readFiles.py 1 real 4m43.378s user 0m1.772s sys 0m4.731s 

Я заметил, что на iotop, когда Perl запускал DISK READ, было около 45 М / с, а IOWAIT – около 70%, при запуске Python DISK READ составлял 2 М / с и IOWAIT 97%. Я не уверен, куда идти отсюда, чтобы сварить их так же просто, как я могу.

В случае, если это имеет значение

 $ python Python 2.7.6 (default, Mar 22 2014, 22:59:56) [GCC 4.8.2] on linux2 $ perl -v This is perl 5, version 18, subversion 2 (v5.18.2) built for x86_64-linux-gnu-thread-multi 

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ ПО ЗАПРОСУ

Я побежал strace и захватил информацию для файла 1000.xml, но все, похоже, делают то же самое:

Perl

 $strace -f -T -o trace.perl.1 perl readFiles.pl 2 32303 open("2/1000.xml", O_RDONLY) = 3 <0.000020> 32303 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff7f6f7b90) = -1 ENOTTY (Inappropriate ioctl for device) <0.000016> 32303 lseek(3, 0, SEEK_CUR) = 0 <0.000016> 32303 fstat(3, {st_mode=S_IFREG|0664, st_size=10000, ...}) = 0 <0.000016> 32303 fcntl(3, F_SETFD, FD_CLOEXEC) = 0 <0.000017> 32303 fstat(3, {st_mode=S_IFREG|0664, st_size=10000, ...}) = 0 <0.000030> 32303 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192 <0.005323> 32303 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 1808 <0.000022> 32303 read(3, "", 8192) = 0 <0.000019> 32303 close(3) = 0 <0.000017> 

питон

 $strace -f -T -o trace.python.1 python readFiles.py 1 32313 open("1/1000.xml", O_RDONLY) = 3 <0.000021> 32313 fstat(3, {st_mode=S_IFREG|0664, st_size=10000, ...}) = 0 <0.000017> 32313 fstat(3, {st_mode=S_IFREG|0664, st_size=10000, ...}) = 0 <0.000019> 32313 lseek(3, 0, SEEK_CUR) = 0 <0.000018> 32313 fstat(3, {st_mode=S_IFREG|0664, st_size=10000, ...}) = 0 <0.000018> 32313 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa18820a000 <0.000019> 32313 lseek(3, 0, SEEK_CUR) = 0 <0.000018> 32313 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192 <0.006795> 32313 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 1808 <0.000031> 32313 read(3, "", 4096) = 0 <0.000018> 32313 close(3) = 0 <0.000027> 32313 munmap(0x7fa18820a000, 4096) = 0 <0.000022> 

Одно замечание, которое я заметил, не уверен, что это важно, заключается в том, что Perl, похоже, запускает это против всех файлов, прежде чем он начнет их открывать, тогда как python не выполняет:

 32303 lstat("2/1000.xml", {st_mode=S_IFREG|0664, st_size=10000, ...}) = 0 <0.000022> 

Также был запущен strace с -c (только что занял несколько первых звонков):

Perl

 $ time strace -f -c perl readFiles.pl 2 % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 44.07 3.501471 23 150018 read 12.54 0.996490 10 100011 fstat 9.47 0.752552 15 50000 lstat 7.99 0.634904 13 50016 open 6.89 0.547016 11 50017 close 6.19 0.491944 10 50008 50005 ioctl 6.12 0.486208 10 50014 3 lseek 6.10 0.484374 10 50001 fcntl real 0m37.829s user 0m6.373s sys 0m25.042s 

питон

 $ time strace -f -c python readFiles.py 1 % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 42.97 4.186173 28 150104 read 15.58 1.518304 10 150103 fstat 10.51 1.023681 20 50242 174 open 10.12 0.986350 10 100003 lseek 7.69 0.749387 15 50047 munmap 6.85 0.667576 13 50071 close 5.90 0.574888 11 50073 mmap real 5m5.237s user 0m7.278s sys 0m30.736s 

Провел ли какой-то синтаксический анализ вывода strace с включенным -T и подсчитал первый 8192 байт, прочитанный для каждого файла, и ясно, что время идет, ниже – общее время, затраченное на 50000 первых чтений файла, за которым следует среднее время для каждого чтения.

 300.247128000002 (0.00600446220302379) - Python 11.6845620000003 (0.000233681892724297) - Perl 

Не уверен, что это помогает!

UPDATE 2 Обновленный код в Python для использования os.open и os.read и просто сделайте одно чтение первых 4096 байт (это будет работать для меня, поскольку информация, которую я хочу, находится в верхней части файла), также устраняет все остальные вызовы в Трассирование:

 18346 open("1/1000.xml", O_RDONLY) = 3 <0.000026> 18346 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.007206> 18346 close(3) = 0 <0.000024> $ time strace -f -c python readFiles.py 1 % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 55.39 2.388932 48 50104 read 22.86 0.986096 20 50242 174 open 20.72 0.893579 18 50071 close real 4m48.751s user 0m3.078s sys 0m12.360s Total Time (avg read call) 282.28626 (0.00564290374812595) 

Все равно не лучше … дальше. Я собираюсь создать виртуальную машину на Azure и попробовать там еще один пример!

ОБНОВЛЕНИЕ 3 – Извинения за размер этого!

Осмотрите некоторые интересные результаты, используя ваш (@JFSebastian) скрипт на 3-х настройках, разделили вывод на старте для краткости, а также удалили все тесты, которые просто быстро выполняются из кеша и выглядят так:

 0.23user 0.26system 0:00.50elapsed 99%CPU (0avgtext+0avgdata 9140maxresident)k 0inputs+0outputs (0major+2479minor)pagefaults 0swaps 

Azure A2 Standard VM (2 ядра 3,5 ГБ RAM Диск неизвестен, но медленный)

 $ uname -a Linux servername 3.13.0-35-generic #62-Ubuntu SMP Fri Aug 15 01:58:42 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ python Python 2.7.6 (default, Mar 22 2014, 22:59:56) [GCC 4.8.2] on linux2 $ perl -v This is perl 5, version 18, subversion 2 (v5.18.2) built for x86_64-linux-gnu-thread-multi (with 41 registered patches, see perl -V for more detail) + /usr/bin/time perl slurp.pl 1 1.81user 2.95system 3:11.28elapsed 2%CPU (0avgtext+0avgdata 9144maxresident)k 1233840inputs+0outputs (20major+2461minor)pagefaults 0swaps + clearcache + sync + sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' + /usr/bin/time python slurp.py 1 1.56user 3.76system 3:06.05elapsed 2%CPU (0avgtext+0avgdata 8024maxresident)k 1232232inputs+0outputs (14major+52273minor)pagefaults 0swaps + /usr/bin/time perl slurp.pl 2 1.90user 3.11system 6:02.17elapsed 1%CPU (0avgtext+0avgdata 9144maxresident)k 1233776inputs+0outputs (16major+2465minor)pagefaults 0swaps 

Сопоставимые первые результаты slurp для обоих, не уверен, что происходило во время второго Perl slurp?

Мой VMWare Linux VM (2 ядра 8 ГБ RAM Disk RAID1 SSD)

 $ uname -a Linux servername 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ python Python 2.7.6 (default, Mar 22 2014, 22:59:56) [GCC 4.8.2] on linux2 $ perl -v This is perl 5, version 18, subversion 2 (v5.18.2) built for x86_64-linux-gnu-thread-multi (with 41 registered patches, see perl -V for more detail) + /usr/bin/time perl slurp.pl 1 1.66user 2.55system 0:13.28elapsed 31%CPU (0avgtext+0avgdata 9136maxresident)k 1233152inputs+0outputs (20major+2460minor)pagefaults 0swaps + clearcache + sync + sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' + /usr/bin/time python slurp.py 1 2.10user 4.67system 4:45.65elapsed 2%CPU (0avgtext+0avgdata 8012maxresident)k 1232056inputs+0outputs (14major+52269minor)pagefaults 0swaps + /usr/bin/time perl slurp.pl 2 2.13user 4.11system 5:01.40elapsed 2%CPU (0avgtext+0avgdata 9140maxresident)k 1233264inputs+0outputs (16major+2463minor)pagefaults 0swaps 

На этот раз, как и прежде, Perl быстрее работает на первом slurp, не уверен, что происходит на втором Perl slurp, хотя раньше этого не видел. Ran measure.sh снова, и результат был точно таким же, что дайте или занять несколько секунд. Затем я сделал то, что сделал бы любой нормальный человек, и обновил ядро, чтобы он соответствовал машине Azure 3.13.0-35-generic и снова запустил measure.sh и не имел никакого значения для результатов.

Из любопытства я затем поменял параметры 1 и 2 в measure.sh, и произошло что-то странное. Перл замедлился, и Python ускорился!

 + /usr/bin/time perl slurp.pl 2 1.78user 3.46system 4:43.90elapsed 1%CPU (0avgtext+0avgdata 9140maxresident)k 1234952inputs+0outputs (21major+2458minor)pagefaults 0swaps + clearcache + sync + sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' + /usr/bin/time python slurp.py 2 1.19user 3.09system 0:10.67elapsed 40%CPU (0avgtext+0avgdata 8012maxresident)k 1233632inputs+0outputs (14major+52269minor)pagefaults 0swaps + /usr/bin/time perl slurp.pl 1 1.36user 2.32system 0:13.40elapsed 27%CPU (0avgtext+0avgdata 9136maxresident)k 1232032inputs+0outputs (17major+2465minor)pagefaults 0swaps 

Это еще больше смутило меня 🙁

Физический сервер (32 ядра 132 ГБ RAM Disk RAID10 SAS)

 $ uname -a Linux servername 3.5.0-23-generic #35~precise1-Ubuntu SMP Fri Jan 25 17:13:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux $ python Python 2.7.3 (default, Aug 1 2012, 05:14:39) [GCC 4.6.3] on linux2 $ perl -v This is perl 5, version 14, subversion 2 (v5.14.2) built for x86_64-linux-gnu-thread-multi (with 55 registered patches, see perl -V for more detail) + /usr/bin/time perl slurp.pl 1 2.22user 2.60system 0:15.78elapsed 30%CPU (0avgtext+0avgdata 43728maxresident)k 1233264inputs+0outputs (15major+2984minor)pagefaults 0swaps + clearcache + sync + sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' + /usr/bin/time python slurp.py 1 2.51user 4.79system 1:58.53elapsed 6%CPU (0avgtext+0avgdata 34256maxresident)k 1234752inputs+0outputs (16major+52385minor)pagefaults 0swaps + /usr/bin/time perl slurp.pl 2 2.17user 2.95system 0:06.96elapsed 73%CPU (0avgtext+0avgdata 43744maxresident)k 1232008inputs+0outputs (14major+2987minor)pagefaults 0swaps 

Здесь Perl, кажется, побеждает каждый раз.

дефлекторы

Учитывая странность моей локальной виртуальной машины, когда я обменивал каталоги, на которых я больше всего контролирую, я собираюсь попробовать двоичный подход для всех возможных вариантов запуска python vs perl с использованием 1 или 2 в качестве каталога данных и попробуйте запустить их несколько раз для согласованности, но это займет некоторое время, и я немного смучу, поэтому сначала может потребоваться перерыв. Все, что я хочу, это последовательность 🙁

ОБНОВЛЕНИЕ 4 – Консистенция

(Ниже выполняется на VMware ubuntu-14.04.1, ядро ​​3.13.0-35-generic # 62-Ubuntu)

Я думаю, что я нашел некоторую согласованность, все время выполнял тесты для Python / Perl slurp на data dir 1/2. Я нашел следующее:

  • Python всегда работает медленно на созданных файлах (т. Е. Создается dd)
  • Python всегда работает на скопированных файлах (т. Е. Создается cp -r)
  • Perl всегда работает на созданных файлах (т. Е. Создается dd)
  • Perl всегда медленно копирует файлы (т. Е. Создается cp -r)

Поэтому я посмотрел на копирование на уровне ОС, и похоже, что Ubuntu «cp» ведет себя так же, как Python, то есть медленнее на исходных файлах и быстро на скопированных файлах.

Это то, что я побежал, и результаты, я сделал это несколько раз на машине с одним SATA HD и системой RAID10, результаты:

 $ mkdir 1 $ cd 1 $ for i in {1..50000}; do dd if=/dev/urandom of=$i.xml bs=1K count=10; done $ cd .. $ cp -r 1 2 $ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' $ time strace -f -c -o trace.copy2c cp -r 2 2copy real 0m28.624s user 0m1.429s sys 0m27.558s $ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' $ time strace -f -c -o trace.copy1c cp -r 1 1copy real 5m21.166s user 0m1.348s sys 0m30.717s 

Результаты трассировки показывают, где время

 $ head trace.copy1c trace.copy2c ==> trace.copy1c <== % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 60.09 2.541250 25 100008 read 12.22 0.516799 10 50000 write 9.62 0.406904 4 100009 open 5.59 0.236274 2 100013 close 4.80 0.203114 4 50004 1 lstat 4.71 0.199211 2 100009 fstat 2.19 0.092662 2 50000 fadvise64 0.72 0.030418 608 50 getdents ==> trace.copy2c <== % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 47.86 0.802376 8 100008 read 13.55 0.227108 5 50000 write 13.02 0.218312 2 100009 open 7.36 0.123364 1 100013 close 6.83 0.114589 1 100009 fstat 6.31 0.105742 2 50004 1 lstat 3.38 0.056634 1 50000 fadvise64 1.62 0.027191 544 50 getdents 

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

Интересно, что «rsyn» и «cp», похоже, работают в противоположных направлениях по скорости, как Perl и Python!

 $ rm -rf 1copy 2copy; sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'; echo "Rsync 1"; /usr/bin/time rsync -a 1 1copy; sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'; echo "Rsync 2"; /usr/bin/time rsync -a 2 2copy Rsync 1 3.62user 3.76system 0:13.00elapsed 56%CPU (0avgtext+0avgdata 5072maxresident)k 1230600inputs+1200000outputs (13major+2684minor)pagefaults 0swaps Rsync 2 4.87user 6.52system 5:06.24elapsed 3%CPU (0avgtext+0avgdata 5076maxresident)k 1231832inputs+1200000outputs (13major+2689minor)pagefaults 0swaps $ rm -rf 1copy 2copy; sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'; echo "Copy 1"; /usr/bin/time cp -r 1 1copy; sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'; echo "Copy 2"; /usr/bin/time cp -r 2 2copy Copy 1 0.48user 6.42system 5:05.30elapsed 2%CPU (0avgtext+0avgdata 1212maxresident)k 1229432inputs+1200000outputs (6major+415minor)pagefaults 0swaps Copy 2 0.33user 4.17system 0:11.13elapsed 40%CPU (0avgtext+0avgdata 1212maxresident)k 1230416inputs+1200000outputs (6major+414minor)pagefaults 0swaps 

  • Как убедиться, что мои запросы AJAX происходят с одного и того же сервера в Python
  • Предотвращение создания новых атрибутов вне __init__
  • Коды символов юникода Python?
  • Не удалось импортировать предметы в scrapy
  • Как вы меняете Pandas DataFrame с помощью multiindex?
  • Пример использования assert в Python?
  • добавить элемент с атрибутами в minidom python
  • Python: elif или new if?
  • One Solution collect form web for “Низкая производительность чтения на Python”

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

    Я думаю, что в этой ситуации может иметь значение Read-Ahead (или, может быть, другой способ, связанный с этим):

    Рассмотрим такой пример:

    Я создал 1000 xml-файлов в директории «1» (имена 1.xml до 1000.xml), как вы это делали с помощью команды dd, а затем я скопировал исходный каталог 1 в директорию 2

     $ mkdir 1 $ cd 1 $ for i in {1..1000}; do dd if=/dev/urandom of=$i.xml bs=1K count=10; done $ cd .. $ cp -r 1 2 $ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' $ time strace -f -c -o trace.copy2c cp -r 2 2copy $ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' $ time strace -f -c -o trace.copy1c cp -r 1 1copy 

    На следующем шаге я отлаживал команду cp (by strace), чтобы узнать, в каком порядке копируются данные:

    Итак, cp делает это в следующем порядке (только первые 4 файла, потому что я видел, что второе чтение из исходного каталога занимает больше времени, чем второе чтение из скопированного каталога)

    100.xml 150.xml 58.xml 64.xml … * в моем примере

    Теперь рассмотрим блоки файловой системы, которые используются этими файлами (вывод debugfs – ext3 fs):

    Исходный каталог:

     BLOCKS: (0-9):63038-63047 100.xml (0-9):64091-64100 150.xml (0-9):57926-57935 58.xml (0-9):60959-60968 64.xml .... Copied directory: BLOCKS: (0-9):65791-65800 100.xml (0-9):65801-65810 150.xml (0-9):65811-65820 58.xml (0-9):65821-65830 64.xml 

    ….

    Как вы можете видеть, в «Скопированном каталоге» блок смежен, поэтому это означает, что при чтении первого файла 100.xml техника «Прочитать вперед» (контроллер или системные настройки) может повысить производительность.

    dd создать файл в порядке 1.xml до 1000.xml, но команда cp копирует его в другом порядке (100.xml, 150.xml, 58.xml, 64.xml). Поэтому, когда вы выполняете:

     cp -r 1 1copy 

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

    Когда вы копируете каталог, который вы скопировали командой cp (поэтому файлы не создаются командой dd), тогда файлы смежны, поэтому создается:

     cp -r 2 2copy 

    копия копии выполняется быстрее.

    Резюме. Чтобы проверить производительность python / perl, вы должны использовать один и тот же каталог (или два файла, скопированные командой cp), а также вы можете использовать опцию O_DIRECT для чтения в обход всех буферов ядра и непосредственного чтения данных с диска.

    Помните, что результаты могут отличаться от разных типов ядра, системы, контроллера диска, системных настроек, fs и т. Д.

    дополнения:

      [debugfs] [root@dhcppc3 test]# debugfs /dev/sda1 debugfs 1.39 (29-May-2006) debugfs: cd test debugfs: stat test.xml Inode: 24102 Type: regular Mode: 0644 Flags: 0x0 Generation: 3385884179 User: 0 Group: 0 Size: 4 File ACL: 0 Directory ACL: 0 Links: 1 Blockcount: 2 Fragment: Address: 0 Number: 0 Size: 0 ctime: 0x543274bf -- Mon Oct 6 06:53:51 2014 atime: 0x543274be -- Mon Oct 6 06:53:50 2014 mtime: 0x543274bf -- Mon Oct 6 06:53:51 2014 BLOCKS: (0):29935 TOTAL: 1 debugfs: 
    Interesting Posts

    Как распределить ось x двух подзаговоров после их создания?

    Запросы Python: не дожидаться завершения запроса

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

    pylab / NetworkX; никакие метки узлов не отображаются после обновления

    Пользовательский кадр Tkinter не работает

    Разделить несколько столбцов на другой столбец в пандах

    как получить номер строки ошибки от exec или execfile в Python

    Отличительные дети родительской модели с наследованием Django

    массива numpy, разрезающего непредвиденные результаты

    Пытается имитировать постоянный байт. Путаница с результатами time.sleep

    Интроспекция вложенных (локальных) функций данной функции в Python

    закрытие python с назначением внешней переменной внутри внутренней функции

    Как получить аутентификацию в телеграмме?

    Selenium в Python на Mac – исполняемый файл Geckodriver должен находиться в PATH

    разбивать большую текстовую (xyz) базу данных на x равных частей

    Python - лучший язык программирования в мире.