LINUX.ORG.RU

Задачка на самый быстрый подсчет встречаемости слов

 


2

2

Привет,

По мотивам темы: Различия между macOS и GNU/Linux

Есть файлик. Вот он: https://disk.yandex.ru/d/XaavsEkOvCT4HQ

Нужно пройтись по файлику и посчитать встречаемость каждого слова в тексте. Словом считается любая последовательность букв от a до z. Регистр нужно привести к одному. Любой другой символ прерывает слово.

Результат записать в другой файл в формате: <количество> <слово>. Например, текст: «cat, cat, cat». Ответ будет такой: «3 cat». Также, слова при выводе нужно отсортировать по их встречаемости.

Например первые несколько строк вывода из приведенного выше файла будут такими:

3343241 the
1852717 and
1715705 of
1560152 to
1324244 a

Дополнительное условие, нужно, чтобы ваша программа отрабатывала быстрее, чем за 7 секунд на Core i5-4690 @ 3.7 GHz.

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

Вроде как BceM_IIpuBeT хотел поучаствовать. Может еще кто-то присоединится.

Я свою штуку написал. Отрабатывает примерно за 5 секунд на Мак-мини 2012-го года, core i7 @ 2,3 GHz.

★★★★★

Последнее исправление: hibou (всего исправлений: 1)

запилил какую-то хрень на C++ и померял стадии выполнения, в секундах.

Input:    2.96073
Process:  0.015366
Output:   0.026757

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

Lrrr ★★★★★
()
Ответ на: комментарий от anonymous

надо иметь в виду, что здесь не используются библиотечные функции, которые ищут и считают слова за тебя

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

<?php
setlocale(LC_ALL, 'C');

$text = strtolower(file_get_contents('./huge.txt'));
preg_match_all("/[a-z]+/", $text, $words);

$count = [];
foreach($words[0] as $word) if (array_key_exists($word, $count)) $count[$word]++; else $count[$word] = 1;

arsort($count, SORT_NUMERIC);
file_put_contents('./res2.txt', var_export($count, true));
~/s/ct ❯❯❯ time php8.0 count2.php

________________________________________________________
Executed in    8,86 secs   fish           external 
   usr time    7,23 secs  1273,00 micros    7,23 secs 
   sys time    1,62 secs  322,00 micros    1,62 secs 

Как видишь, отмазка не прокатила.

no-such-file ★★★★★
()

Самое длинное слово в тексте:

Freundschaftsbezeigungenstadtverordnetenversammlungenfamilieneigenthümlichkeiten

Файл косячный, заявленному UTF-8 не соответствует.

anonymous
()
Ответ на: комментарий от Lrrr

вообще все зависит от скорости ввода

Что-то фигню намерил, у меня чисто чтение занимает доли секунды. Так-то файл маленький, должен быть целиком в буфере, так что там негде тормозить.

no-such-file ★★★★★
()
Ответ на: комментарий от anonymous

правильно делают

Так это защита от нубов, вроде тебя. Чтобы не ставили машину раком. А так-то прикинь, чтобы обработать 350М нужно сравнимое количество памяти, если ты не хочешь до завтра ждать результат.

no-such-file ★★★★★
()
Ответ на: комментарий от Rost

сравнить пых с перлом

Пых самый быстрый в своём классе.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Код неправильный, str_word_count считает - как часть слова. Это правильно и логично, но ТС поставил другую задачу.

fernandos ★★★
()
Ответ на: комментарий от no-such-file

Так тыкать пальцем проще, чем выучить язык.

grem ★★★★★
()
Ответ на: комментарий от no-such-file

Не может быть, ваш же код.

➜ hyperfine 'php8.0 -dopcache.enable_cli=1 -dopcache.jit_buffer_size=128M strtr.php' 'php8.0 -dopcache.enable_cli=1 -dopcache.jit_buffer_size=128M repl.php'
Benchmark #1: php8.0 -dopcache.enable_cli=1 -dopcache.jit_buffer_size=128M strtr.php
  Time (mean ± σ):      6.368 s ±  0.056 s    [User: 4.964 s, System: 1.400 s]
  Range (min … max):    6.282 s …  6.442 s    10 runs
 
Benchmark #2: php8.0 -dopcache.enable_cli=1 -dopcache.jit_buffer_size=128M repl.php
  Time (mean ± σ):      6.585 s ±  0.126 s    [User: 5.086 s, System: 1.485 s]
  Range (min … max):    6.390 s …  6.832 s    10 runs
 
Summary
  'php8.0 -dopcache.enable_cli=1 -dopcache.jit_buffer_size=128M strtr.php' ran
    1.03 ± 0.02 times faster than 'php8.0 -dopcache.enable_cli=1 -dopcache.jit_buffer_size=128M repl.php'
➜ cat strtr.php
<?php
ini_set('memory_limit', '-1');
setlocale(LC_ALL, 'C');
$text = strtr(strtolower(file_get_contents('./huge.txt')), "-'", '  ');
$words = str_word_count($text, 1);
$count = array_count_values($words);
arsort($count, SORT_NUMERIC);
file_put_contents('./res2s.txt', var_export($count, true));
➜ cat repl.php
<?php
ini_set('memory_limit', '-1');
setlocale(LC_ALL, 'C');
$text = str_replace(['-', "'"], ' ', strtolower(file_get_contents('./huge.txt')));
$words = str_word_count($text, 1);
$count = array_count_values($words);
arsort($count, SORT_NUMERIC);
file_put_contents('./res2r.txt', var_export($count, true));
fernandos ★★★
()
Ответ на: комментарий от Rost

интересно было бы сравнить пых с перлом

Не знаю хорошее ли ныне core у Perl, а раньше алгоритмы работы с текстом работали супер быстро /не зря его для парсинга логов использовали/.
Приведу маленький пример.
Когда-то на Foxpro 2 разработал много разных бухгалтерских армов.
Но Foxpro 2 супер медленно работает с текстом.
Прикрутил к нему Perl.
Так вот к примеру создание отчета, содержащего расчетные листки /2000 штук/ шла доли секунды.
Попробовал для баловства их все расположить друг за другом по горизонтали

доли секунды

Каков ныне Perl не знаю …

anonymous
()
Ответ на: комментарий от WitcherGeralt

Ну финально да, в конце концов берём эталон и меряем на нём. Только вот тут соревнования настолько абстрактные что участвовать не интересно :(( Первая половина от лабды пишет, вторая код пишет, третья стандартные утилиты юзает. Чего тут измеряют я так и не понял =) Но в целом у всех плюс минус всё одинаково +/- 2 секунды как погрешность разности железа.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

нудак huge.txt надо было переименовать в small.txt

anonymous
()
use hashbrown::HashMap;
use std::fs::File;
use std::io::{BufRead as _, BufReader};

fn main() {
    let file = File::open("huge.txt").expect("Can't open file");
    let mut reader = BufReader::with_capacity(1024 * 1024, file);
    let mut counters: HashMap<Vec<u8>, u64> = HashMap::new();
    let mut word = Vec::new();

    loop {
        let buf_len = {
            let buf = reader.fill_buf().expect("Reading failed");

            if buf.is_empty() {
                break;
            }

            for byte in buf.iter() {
                match byte {
                    b'a' ..= b'z' => word.push(*byte),
                    b'A' ..= b'Z' => word.push(b'a' + (*byte - b'A')),
                    _ => {
                        if !word.is_empty() {
                            if let Some(counter) = counters.get_mut(&word) {
                                *counter += 1;
                            } else {
                                counters.insert(word.clone(), 1);
                            }
                            word.clear()
                        }
                    }
                }
            }

            buf.len()
        };
        reader.consume(buf_len);
    }
    let mut counters: Vec<_> = counters
        .into_iter()
        .map(|(word, count)| (count, String::from_utf8(word).unwrap()))
        .collect();
    counters.sort_unstable_by(|l, r| r.cmp(l));
    for (count, word) in counters.iter() {
        println!("{} {}", count, word);
    }
}
hyperfine ./target/release/count-words
Benchmark 1: ./target/release/count-words
  Time (mean ± σ):      3.143 s ±  0.113 s    [User: 2.818 s, System: 0.324 s]
  Range (min … max):    2.987 s …  3.328 s    10 runs
anonymous
()

Я свою штуку

Неправильно измеряешь. Вот как надо.

rupert ★★★★★
()
Ответ на: комментарий от anonymous

Интересно время работы алгоритма без использования STL.
STL, то конечно как-бы упрощает код, но местами он ТУПОВАТ …

anonymous
()
Ответ на: комментарий от no-such-file

Чё за проц?

i3-9100f, самое близкое к Core i5-4690 @ 3.7 GHz из того, что есть.

fernandos ★★★
()
Ответ на: комментарий от no-such-file

В смысле «хлопок газа»?

А я понял о чем вы.
Но у него нет только «выхлоп» …

anonymous
()
Ответ на: комментарий от anonymous

это и есть его ВЫХЛОП

даладно - dc установи… выхлоп имеет следующий вид

193236060171271332087785604739148462136540850023109634354176769542513\
450459760692886729046857234760078127160930788381267275716029504619746\
769141229171070188928463568538679833185648187049077227652717176891297\
483129424668815520603687413970275896469294732531037983134647866324669\
665609938993928547290573838996165634382651623419572382344008170554615\
992557628483466631932849600120665842127659878430747184039181061674903\
601415841646896264733243115541351556257780649335455243556667314361074\
638767116850976060195890369053078165038348852431436784919273309785127\
807385782314883445831078955977629289904379383694830045313324862712905\
130392182400311887769702385900864531404180157392210451109149334352832\
955082253422147803866524648803025196834084094025745156069590766980613\
522935820520886598714639276763237791246202796497995433189057035655446\
571107863914020332507684395274334609088675457245556806048566252312307\
027270413943378675340519405729420215963379660034808227310130177180990\
452978537098693507029348913280821987354517292735252430401859759333834\
405808704771379527705534215134936452203857457762099271778804049352188\
662508987614314727511549708820437195889059606538174880968431106943454\
094257074037539145432539603152261892296167997552624170652610353164614\
183499848950047415639370700207357515615224621427027658941558327633934\
080543927617700912740834165799704976707014821959315657691202934777262\
701366897071113522700649247615988849871534811311256902478902611555961\
325734397372556642444656624963457861510805263452923338998466557599865\
859073173828488058023561540455264759779614514978001324584892148499525\
596644308753234882221867562715319176413870819519515918793312269732659\
926242088334712251258022889731874275365216609056086956268078132436195\
146010132059303035112745859358168023200069311673376829060862717435073\
016282829739195674088098325182671557703431197457408959380268500697978\
415823490669875537735421015504811413450623029586422163881489630044330\
995406342456858109402802698250564848913831643105672087479084040617225\
951290717603350384530690820905304563784517639402962909266476874495530\
448501821149863587108645884865130614187676113904739754537204423401405\
265885391359281218526396476889195210848117570970593597448229589349677\
362158000634556774077866074369615401221097864514794738449457876725782\
103073530760291688452440754492582447562813268507792002256391564008130\
640963798746130677219247329082158426961352383293566749712121723375040\
757722153320439066626755095705487175132238773137474513774985946673486\
224866703476007772048201136586336203688757246486979587964813332916874\
313326090810221795306680175738236617361557708927159642417874234658126\
830700297942501475752237437722581279570876814400646181230768518654423\
846681022137278800053812455386759478474248529217699391478186271410791\
753323555501917472860897056463068773566966783854031889978352184784166\
4834323410018255732662370891387109376

real	2m50,914s
user	2m50,558s
sys	0m0,142
amd_amd ★★★★★
()
Ответ на: комментарий от anonymous

Голословное утверждение. В чём туповат?

Пост был о STL в Windows.
Ради интереса пробовал его дебажить, так вот для выполнения какого-либо API уровень вложенности использования функций бывает ЗАШКАЛИВАЕТ …

Это не голословно ...

Так как камни ныне весьма эффективны, то создается впечатление, что многие из алгоритмов эффективны …
Потому ныне многие программы и ТУПЯТ.

ШИБКО УМНЫЕ ...
anonymous
()
Ответ на: комментарий от anonymous
CPU: Intel i7-3770 (8) @ 3.900GHz

time echo '2 10000000 ^ p'|dc
772192248950779024515016843066881401412217017216751421634503592305757\
----------------------------------------------------------------------
753323555501917472860897056463068773566966783854031889978352184784166\
4834323410018255732662370891387109376

real	1m50,821s
user	1m50,737s
sys	0m0,081s
anonymous
()
Ответ на: комментарий от amd_amd

лучше расскажи как 5 звезд нафлудил

Давно тут сидим (с). Вообще я активно флудил до 3 звезд. А потом забил, но оно как-то само.

no-such-file ★★★★★
()
Ответ на: комментарий от anonymous

Это всё та же голословщина. Ты же не рассчитываешь, что хоть кто-то воспримит «ну я ж дебажил!1 Там больше одного слоя» как аргумент?

anonymous
()
Ответ на: комментарий от anonymous

Там больше одного слоя» как аргумент?

Аргумент.
Потому что говорю о том в чем разбираюсь …

anonymous
()

Написал многопоточный вариант, но есть одна проблема - не могу сейчас его проверить на чём-то достойном, мой ноут очень слабый:

#include <fstream>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <string>
#include <vector>
#include <thread>
#include <stdexcept>
#include <algorithm>
using namespace std;

struct Task {
    size_t begin, end;
    unordered_map<string, size_t> res;
};

bool check_char(char *c) {
    if (*c >= 'A'  &&  *c <= 'Z')
        *c -= 'Z' - 'z';
    return *c >= 'a'  &&  *c <= 'z';
}

void make_dict(Task *task, unique_ptr<char[]> *text, size_t sz) {
    auto add = [&task, &text](size_t b, size_t e) {
        ++ task->res[string(&(*text)[b], &(*text)[e])];
    };
    size_t pos = task->begin;
    task->begin = -1;
    for (;  pos != task->end;  ++ pos)
        if (check_char(&(*text)[pos])) {
            if (task->begin == -1)
                task->begin = pos;
            if (task->end - pos == 1)
                add(task->begin, pos + 1);
        }
        else if (task->begin != -1) {
            add(task->begin, pos);
            task->begin = -1;
        }
}

int main() {
    unique_ptr<char[]> text;
    size_t sz;
    { //block
        ifstream is{"huge.txt", ios::binary | ios::ate};
        if (! is)
            throw logic_error("file was not found");
        sz = is.tellg();
        text = make_unique_for_overwrite<char[]>(sz);
        is.seekg(0);
        if(! is.read(&text[0], sz))
            throw logic_error("read error");
    }

    vector<Task> tasks;
    { //block
        if (! thread::hardware_concurrency())
            throw logic_error("unknown number of cores");
        size_t pos = 0;
        while (pos != sz) {
            tasks.push_back(Task{pos});
            pos += sz / thread::hardware_concurrency();
            for (;  pos < sz  &&  check_char(&text[pos]);  ++ pos);
            if (pos >= sz)
                pos = sz;
            else
                ++ pos;
            tasks.back().end = pos;
        }
        vector<jthread> th;
        for (Task &t : tasks)
        th.emplace_back(make_dict, &t, &text, sz);
    }

    vector<pair<string, size_t>> res;
    for (size_t i = 0;  i < tasks.size();  ++ i) {
        for (auto &el_from_cur : tasks[i].res) {
            for (size_t i_next = i+1;  i_next < tasks.size();  ++ i_next) {
                auto nh = tasks[i_next].res.extract(el_from_cur.first);
                if (nh)
                    el_from_cur.second += nh.mapped();
            }
            res.push_back(move(el_from_cur));
        }
        sort(res.begin(), res.end(), [](auto &f, auto &s) {
                    return f.second > s.second;});
    }
    for (size_t i = 0;  i < 20;  ++ i)
        cout << res[i].second << "   " << res[i].first << '\n';
}
$ g++ 1.cpp -O3 -pthread -std=c++20
$ time ./a.out 
3343241   the
1852717   and
1715705   of
1560152   to
1324244   a

real    0m7.368s
user    0m13.463s
sys     0m0.517s

Ребята, ради интереса, запустите у себя, компилять g++ 1.cpp -O3 -pthread -std=c++20 ну или clang++ ...

kvpfs ★★
()
Ответ на: комментарий от BceM_IIpuBeT

Спасибо, полторы секунды, неплохо. Можно было ещё заморочиться с резервированием памяти в контейнерах.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

Core2Duo T9600 на HDD:

real    0m4.302s
user    0m7.840s
sys     0m0.322s

Кстати, gcc10 ругается

main.cpp:50:16: error: ‘make_unique_for_overwrite’ was not declared in this scope
   50 |         text = make_unique_for_overwrite<char[]>(sz);
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BceM_IIpuBeT ★★☆☆☆
()
Ответ на: комментарий от BceM_IIpuBeT

Да, это новая фича из цпп20, не инициализирует буфер при аллокации, у меня это экономит примерно 0.1 сек.

kvpfs ★★
()
Ответ на: комментарий от BceM_IIpuBeT

С посимвольным вводом

Ну там же буффер должен быть, даже не один. А вообще я удивляюсь зачем все эти телодвижения, вместо того чтобы заммапить файл.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Ты там ничего не выйграешь и вообще - файл читается в буфер на моём HDD за примерно 0.4 сек. Не знаю точно, но похоже на горячий старт и какой-то кэш ОС, хотя файл большой очень, но хз как может быть так быстро тогда иначе.

kvpfs ★★
()
Ответ на: комментарий от no-such-file

Всё равно херота. На уровне пыха в один поток.

На твоей числодробилке там время выполнения, наверное, и секунды не займёт.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

Можно было ещё заморочиться с резервированием памяти в контейнерах.

У меня это даёт ускорение в 900мсек (real) и 1618мсек (user).

kvpfs ★★
()
Ответ на: комментарий от no-such-file

А то что ты читерок вывел только первые 10 строк тебя не смущает? head как бы дропает пайп после этого и полного вывода не происходит.

Я в курсе. Это не сильно влияет на производительность. Те же 18 секунд.

$ time perl -ne '$h{lc($1)}++ while (/([A-Za-z]+)/gc); END { printf "$h{$_} $_\n" for (sort {$h{$b} <=> $h{$a}} keys(%h)) }' huge.txt > out

real    0m18.404s
user    0m18.334s
sys     0m0.052s

Ну ладно, пых быстрее в данном случае. Хоть и память жрёт как не в себя. 4 гига у меня отожрал в пике. Когда у перла всего 60 метров. Если твоей проге скормить файл раз в 10 больше, она прибивается оом-киллером за 20 секунд.

anonymous
()
Ответ на: комментарий от kvpfs

Ryzen 4700U

hyperfine ./a.out
Benchmark 1: ./a.out
  Time (mean ± σ):      1.483 s ±  0.089 s    [User: 5.296 s, System: 0.413 s]
  Range (min … max):    1.380 s …  1.576 s    10 runs
anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.