Проблема с медленным чтением в Python

56

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

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

Я создал свои тестовые данные, 50 000 файлов 10 КБ (это отражает размер файла 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);
}

Python

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

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

Я побежал и схватил информацию для файла 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>

Python

$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

Python

$ 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

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

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

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 - Извините за размер этого!

Хорошо, некоторые интересные результаты, используя ваш (@J.F.Sebastian) script на 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, и произошло что-то странное. Perl замедлился, и 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 - Консистенция

(Ниже выполняется на VM-сервере ubuntu-14.04.1, ядро ​​3.13.0-35-generiС# 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
  • 0
    Кажется, проблема не связана с процессором. Можете ли вы strace оба, чтобы сравнить, как они читают файл?
  • 0
    @nneonneo Я думал об этом, но данных было много, я провел несколько тестов и включил, как я надеюсь, правильный уровень информации, единственное очевидное отличие, которое я вижу, это использование mmap и munmap, хотя я не уверен, что приходится все потерянное время?
Показать ещё 39 комментариев
Теги:
performance
io

1 ответ

22
Лучший ответ

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

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

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

Я создал 1000 xml файлов в директории "1" (имена 1.xml до 1000.xml), как вы это делали с помощью команды dd, а затем я скопировал оригинал dir 1 в dir 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 "Read Ahead" техника (контроллер или системные настройки) может повысить производительность.

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:  
  • 0
    Это отличная информация, я начал приходить к аналогичному выводу в отношении способа хранения данных на диске между «оригинальными» файлами и копиями. Я собираюсь поиграть и посмотреть, не сводится ли это к разному порядку чтения Perl и Python из дерева каталогов по умолчанию. Как я знаю, даже если я протестирую и Python, и Perl в одном и том же каталоге, результаты будут отличаться независимо от того, оригинал он или скопирован. debugfs тоже полезен, это новый инструмент для меня :-)
  • 0
    Да :), тогда вы можете поиграть с: Как Python / Perl читает файлы (в каком порядке) и как файлы помещаются на диск.
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню