Sortowanie – Bash vs Perl benchmark

Dziś na kanale IRC znajomy szukał błędu w skrypcie bashowym. Danych dużo, skrypt wykonuje się długo (kilkadziesiąt minut). Błąd pojawiał mu się w sort, skrypt złożony. Jakoś tak stwierdził, że przepisze na Perla, będzie szybciej. Tyle tytułem wstępu, bo potem rozpoczęliśmy akademickie rozważania pt. czy sortowanie w Perlu będzie szybsze.

Postanowiłem się pobawić w praktykę. Nie ukrywam, że sporą rolę maiła tu niedawna dyskusja nt. algorytmów (i czemu zabawy z nimi są fajne) z M.[0]

Najpierw wygenerowanie sporego zestawu danych testowych [1]. 10 mln liczb z zakresu 0-99, 28999760 bajtów.

Następnie prosty sort [2]. Wyniki oscylują w granicach 16-18 sekund.

Pierwszy skrypt w Perlu[3] Jest minimalnie szybciej, bo 15 sekund.

Widać parę niepotrzebnych przepisań i da się to skrócić [4]. Po skróceniu wyniki oscylują w granicach 14,4 sekundy.

Pora na mały cheat, bo wiemy, że większość danych się powtarza. Czyli wersja z hashem – zliczamy ilości wystąpień, a na koniec sortujemy tyko klucze hasha (których jest 100) i wyświetlamy tyle razy, ile razy wystąpiły [5].

Jest dużo szybciej. Okolice 4,7 sekundy. Szczerze mówiąc nie sądziłem, że aż tyle da się urwać.

Na koniec postanowiłem sprawdzić, czy różnica wynika z powtarzających się danych. Wygenerowałem mały plik z praktycznie niepowtarzającymi się danymi [6]

Tutaj sort wykonywał się ok. 70 ms, natomiast – ku mojemu zdziwieniu – Perl nadal był szybszy i wykonywał się ok. 50 ms.

Daleki jestem od wyciągania z tego benchmarku daleko idących wniosków, nikogo też na przechodzenie wszędzie na Perla nie namawiam. Bardziej chodziło mi o pokazanie ciekawostki. Zwykle 10 sekund różnicy nie ma znaczenia, narzut na pisanie nie zwróci się nigdy, a wersja z sort jest po prostu czytelniejsza. I pewnie nadal będę używał sort. Niemniej warto wiedzieć, że w prosty sposób da się szybciej. Taki mały hack.

Inna sprawa, że IMVHO pisanie skryptów w bashu zwykle nie ma sensu i lepiej użyć Perla, Pythona itp. Szybciej, więcej można w prosty sposób zrobić, a skrypty bashowe mają tendencję do puchnięcia z elementami dziwnych rzeźb.

Uwagi: Wynik sortowania jest identyczny, choć nie to było priorytetem. Sortowanie nie jest numeryczne (jak ktoś chce, może się bawić, stawiam, że wyniki będą podobne). Skrypty są pisane mało optymalnie, mało czytelnie itd. ale tak na szybko piszę onelinery z głowy, a nie bawiłem się w upiększanie, bardziej chciałem szybko notkę zrobić. Tak, wiem nie ma close (F), w skryptach, ale nie jest tu potrzebne. Nie ma potrzeby korzystania z open, spokojnie można było robić „tradycyjne” while (<>) i czytać ze STDIN; jak sprawdziłem później nie powoduje to zauważalnej różnicy w czasie wykonania.

Ponieważ Blox wyczynia cuda z tekstami w pre podczas edycji notki, efektem czego jest znikanie większej części skryptów i tekstu, poniżej wklejka z wszystkimi poleceniami (odnośniki powyżej).

 

[0] Jest szansa, że pojawi się parę zadań z algorytmami. Ale niczego nie obiecuję. Zresztą, chyba lepiej, żeby takie rzeczy działy się na blogu jakiegoś programisty… A jak zobaczyłem, co robi Blox z groźnymi nawiasami trójkątnymi, to zupełnie odeszła mnie ochota na pisanie czegoś wymagającego fragmentów kodu tutaj.

UPDATE: Jak trafnie zauważyli czytelnicy w komentarzach, sort można przyspieszyć (analogicznie, jak przyspieszyć można grep) poprzez ustawienie LC_ALL=C lub LANG=C. Po tym zabiegu sort, dla podanych danych, działa szybciej od dotychczasowego najszybszego Perla, w okolicach 4,2 sekundy.

Jak zrobić kuku spamerowi?

Jak wielu innych ludzi, darzę spamerów czystym i płomiennym uczuciem. Jakiś czas temu popełniłem automat do wykrywania spamu na Blox. W zasadzie, to tych skryptów jest kilka i raczej dane zbierają się „na kiedyś”, niż działa to produkcyjnie, ale czasem coś tam podeślę do blokowania. Niemniej, nie jest to pełny automat.

Jeśli chodzi o pocztę, to nie jest u mnie ze spamem źle. Łącznie na wszystkie konta dostaję jakieś małe pojedyncze sztuki dziennie. Część odsiewają dostawcy poczty, przytłaczającą większość tych nielicznych, które przejdą oznacza Thunderbird.

Dodatkowo, jeśli już coś do mnie dotrze, to zwykle trafia na Spamcopa (polecam zarejestrowanie się). Roboty z tym tyle, co kliknięcie Forward i linka w mailu, więc niewiele, a powiadamiane są wszystkie powiązane abuse. Polecam rejestrację. Czasem nawet odpiszą, że zablokowali (i to niekoniecznie nadawcę, bo potrafią reagować także właściciele domen/hostingów na których znajduje się „reklamowana” strona. Tak czy inaczej, o ile nie zadziała to pewnie na spam o niebieskich pastylkach wysyłanych z botnetów (ale liczę, że to odpada na etapie dostawcy poczty, zresztą mało tego typu dociera do mnie), to działa[1] na byznesmenów wysyłających zapytania o możliwość wysłania oferty handlowej do adresatów z baz pozyskanych z ogólnodostępnych źródeł. Znaczy, tłumacząc na polski: na spamerów wysyłających spam, bo (polskie) prawo prawem, ale o tym, czy wiadomość była zamówiona decyduje jednak odbiorca.

Niedawno, podczas szukania rozwiązań antyspamowych trafiłem na ciekawą stronę Email Labirynth, która generuje losowe adresy email w celu zaśmiecenia baz danych harvesterom, a w konsekwencji spamerom. Czyli po pierwsze stracą czas zbierając te adresy, po drugie stracą czas wysyłając maile na nieistniejące adresy, a po trzecie jest spora szansa, że dzięki takim wysyłkom trafią na RBLe. Nie jestem przekonany o skuteczności, ale spróbować IMO nie zaszkodzi. Sceptykom twierdzącym, że spamerzy nie mogą być aż tak głupi od razu mówię, że nie tylko mogą, ale są. Może nie wszyscy, ale większość. No i zwykle mają słabe automaty, a nadzór ludzki kosztuje.

W każdym razie powyższe rozwiązanie ma IMO kilka wad:

  • Brak spamtrapa na każdej stronie. IMHO na każdej(?) stronie wśród generowanych maili powinien być także spamtrap w celu automatycznego zgłaszania IP korzystających z harvestowanych adresów email do abuse/RBLi.
  • Stały adres strony. Wystarczy szczątkowa inteligencja, by nie harvestować tam adresów email.
  • W pełni losowe loginy. Trochę wada, trochę zaleta. W każdym razie wyglądają mało naturalnie i przy odrobinie wysiłku można je odsiać.
  • Brak lokalizacji. Wiadomo, że spamerzy celują z niektórymi produktami raczej w określone grupy klientów, np. klientów z Polski. Dane z ww. strony zdecydowanie nie wyglądają na bazę polskich klientów email.

Koniec końców postanowiłem zrobić swoje rozwiązanie realizujące podobny cel (oczywiście Perl). Na razie mam opracowane częściowe rozwiązanie[2] dla dwóch ostatnich punktów. Punkt drugi też będzie rozwiązany, bo zamierzam opublikować gotowca, którego każdy będzie mógł podpiąć na swojej stronie.

Ponieważ pewnie trochę czasu będę miał dopiero w przyszły weekend, liczę do tego czasu na uwagi dot. sensowności i ew. innych funkcjonalności.

[1] Działa, znam trochę środowisko hostingowe. Przyzwoite hostingi nie przepadają za wysyłającymi spam do zaśmieconych baz adresów email, a z tego co wiem w polskich firmach hostingowych abuse raczej działa.

[2] Jak dam sobie na luz z perfekcjonizmem, to pewnie uznam je za docelowe, przynajmniej w pierwszej wersji.

UPDATE: No i uruchomiłem. Póki co wersja testowa karmnika z adresami email wisi tu.

Własna namiastka dyndns

Po ostatniej akcji Microsoftu z przejęciem i zablokowaniem domen należących do No-IP miałem przez moment umiarkowany problem z dostępem do jednej z moich maszyn, bo nie znałem IP. Nic krytycznego i szybko rozwiązałem, ale stwierdziłem, że warto by się zabezpieczyć na przyszłość. Początkowo chciałem uruchomić po prostu innego dostawcę dyndns, z inną domeną, co dałoby całkiem niezłą – jak mi się wydaje – redundancję w tym względzie, ale prowizorycznie uruchomiłem coś innego, własnego i znacznie prostszego. W komentarzu do poprzedniego wpisu padło pytanie co dokładnie zrobiłem, więc opisuję.

Tak się składa, że mam serwer dedykowany ze stałym IP (nie jest konieczne zamiast tego można skorzystać z domeny), na którym stoi serwer WWW (lighttpd). Na wszystkich maszynkach potrzebujących dyndns mam dostęp do crona (zresztą, korzystałem z crona już przy zwykłym koncie dyndns, patrz update wpisu). Rozwiązanie jest proste: klient wywołuje okresowo unikatowy URL na moim serwerze, serwer okresowo parsuje log w poszukiwaniu tego unikatowego ciągu znaków i zapisuje IP z którego nastąpiło odwołanie w określonym pliku.

Poniżej wklejki ze wszystkimi onelinerami (gotowiec dla lighttpd, w przypadku innego serwera WWW trzeba dostosować):

# po stronie klienta*/5 * * * * /usr/bin/wget -4 -q -O /dev/null http://mojadomena.com/losowyciagznakow4346456543324645 > /dev/null
 # po stronie serwera*/5 * * * * /usr/bin/awk '/losowyciagznakow4346456543324645/ {ip=$1} END {print ip}' /var/log/lighttpd/access.log > /var/www/dyndns_host1.txt
 # pobranie IPwget -O - http://mojadomena.com/dyndns_host1.txt

Po namyśle, rozwiązanie nawet lepsze niż drugi dostawca dyndns. Mniej kont, prostsza konfiguracja, mając stałe IP na upartego można całkowicie uniezależnić się od DNSów, jeśli ktoś odczuwa potrzebę.

Oczywiście nie od razu tak to wyglądało, w szczególności po stronie serwera był grep, awk, tail w użyciu. Ale skoro da się wszystko załatwić awk (a nie tylko wyświetlanie określonej kolumny, chyba najczęstsze zastosowanie awk…), to czemu nie? Tu polecam zbiór przydatnych onelinerów w awk.

UPDATE: IPv6 się popularyzuje. Okazało się, że mój skrypt nie działa poprawnie, bo łączenie między maszynami następuje po IPv6. Dodany parametr -4 do wget w celu naprawy tego problemu.