Temat: Zrzędzenie na interaktor

W tym roku mieliśmy przyjemność lub nieprzyjemność mierzyć się z zadaniami typu double run/komunikacyjnymi/zwał jak zwał. Same w sobie zadania były bardzo fajne i miałem sporą frajdę w ich rozkminianiu, pochwała dla Adama za wymyślenie takich ciekawych zadań. Jednak obraz wrażeń z pracy nad tymi zadaniami bardzo mocno popsuł interaktor/*run.py, który jest jakimś niedopracowanym półproduktem i przygotowanie zadań pod kątem technicznym kulało i mi osobiście sprawiło dużo cierpienia i spróbuję tu wymienić opisać szereg owych cierpień i wskazać wady, które mam nadzieję, że zostaną poprawione na przyszłość.

Zwrócę uwagę, że ten skrypt run.py ma jakieś 400 linii i jest niezbyt przejrzysty. Jak chce się mieć jakiś czas na robienie zadań, to trochę nie ma jak go dogłębnie analizować i zawodnicy powinni móc go traktować jako czarną skrzynkę, do której nie powinni i nie potrzebują zaglądać, aby móc w pełni wygody pracować nad zadaniem i umieć robić podstawowe rzeczy w stylu normalnie debugować swój kod lub mierzyć czas wykonania (i to samo też się tyczy interaktorów soc.cpp, choć ich problem o tyle mniejszy, że są one przejrzystsze)

1) run.py kompiluje za każdym razem nasz program od zera ... czemu :|?
Wyjątkowo dziwny pomysł nastręczający szeregu problemów. U mnie się objawił tak, że nie widziałem stderr swojego programu (bo jak każdy rozsądny człowiek stderr wypisuję tylko jeżeli program jest skompilowany z flagą LOCAL, której run.py oczywiście nie dodawał), co de facto sprawiało, że nie za bardzo jestem w stanie jakkolwiek debugować swój program i widzę jedynie OK/WA (debugowanie przez couty oczywiście nie wchodziło w grę, a jakby ktoś umiał jakieś czary w stylu używanie debuggerów do podglądania wartości zmiennych, to zakładam, że też jest skazany na niepowodzenie z tym całym setupem). Na szczęście się ogarnąłem, że mogę chociaż widzieć zgłaszane znaki poprzez modyfikację pknsoc.cpp, ale to trochę mało, bo wolałbym mieć dostęp do pośrednich danych swojego algorytmu. Założyłem, że w tych 400 linii stawiania pipów gdzieś mój stderr jest zjadany i ignorowany, a się właśnie końcowo okazało, że ktoś wpadł na cudowny pomysł kompilowania mojego programu bez moich domyślnych flag. Moim zdaniem należy to jak najszybciej wywalić z tego interaktora, albo at the very least informować caps lockiem w treści zadania, że ten interaktor robi takie bardzo dziwne coś. No i poza jasną wadą o innych flagach jest też inna jasna wada w postaci, że z każdym uruchomieniem trzeba czekać parę sekund na kompilacje naszego programu.

2) Niewidoczny cout interaktorów.
Interaktory na koniec interakcji miały napisane wypisywanie na stdout, że wyszło w iluśtam zapytaniach albo że WA z jakiegośtam powodu albo że format ina zły itd. No właśnie, ale nie przekładało się to jakkolwiek na rzeczywistość, bo nie wiem co się z tym stdoutem interaktora dzieje, ale na pewno nie wypisywał się nigdzie w widocznym dla mnie miejscu i liczbę zużytych zapytań widziałem jedynie w podpisie zgłoszenia po submitnięciu na SIO. Ciężko mi uwierzyć, że to nie jest po prostu bug w run.py, bo dość oczywistym intended zachowaniem jest to, że te komunikaty powinne być widoczne dla zawodników przy uruchamianiu lokalnym? Było to jeszcze o tyle nieprzyjemne, że gdy coś z interakcji szło nie tak i interaktor przestawał z nami gadać, to nie było widać od niego tego komunikatu i zaczynały się wyświetlać bardzo mylące błędy sugerujące, że mamy bugi w programie, a był to np. zły format ina. Widać, że jurorzy sami nie za bardzo wiedzieli co się gdzie wypisuje, bo komenda do uruchamiania interaktora w treści miała "> [wyjscie]" sugerujące jakby coś się miało na ten stdout wypisywać, mimo że nic się nie wypisywało. Co więcej, zwróciłem na to dość szybko uwagę w pytaniu i została mi w odpowiedzi przyznana racja, ale zamiast być poprawione w 5 minut, to nie zostało to w ogóle poprawione do końca rundy?

3) Brak uruchomienia próbnego w PKN.
Nie rozumiem czemu go nie było. Skoro da się submitnąć to zadanie, to czemu postawienie uruchomienia próbnego było problematyczne? W KOD się już jakoś udało, więc chyba nie była to jakaś przeszkoda w SIO niezależna od jurorów PA? Mocno bolało biorąc pod uwagę, że to jasne, że ze wszystkich zadań to właśnie w tym byłoby najprzydatniejsze. Ale nawet będąc już postawionym przed faktem dokonanym, że owych uruchomień nie ma, to aby temu jakoś zaradzić powinny być właśnie chociaż zwiększone limity submitów. Prosiłem o to w pytaniach i owa prośba została odrzucona pomimo tego, że wydaje mi się zupełnie jasne, że jurorzy powinni się wykazać świadomością sytuacji jakim bałaganem od strony technicznej jest to zadanie i że w obliczu braku uruchomień próbnych zwiększenie limitu submitów jest dość oczywistym częściowym zamiennikiem (następnego dnia owy limit został zwiększony, ale z jakichś mniej istotnych powodów no i dla mnie too little too late). Brak uwzględnienia tej prośby oceniam bardzo negatywnie i niewątpliwie był to jeden z czynników, przez które moje cierpienia z PKN się wydłużyły o kilka godzin.

4) Niejasne liczenie czasu.
Z wielu różnych śmiesznych powodów jak kompilowanie od zera naszego kodu przez run.py czy inne śmiesznostki, czas działania komendy z treści jest jedynie luźno powiązany z faktycznym czasem działania naszego programu. W treści zadania jest jakoś zdefiniowane co się jako ten czas liczy, ale to za mało i powinien być dostarczony namacalny sposób jak go zmierzyć. Ja lokalnie na małym samplu miałem czas działania całości 3s, a na dużym samplu 4s, zatem faktyczny czas działania mojego programu wynosił 1s, a na sprawdzarce miałem mały sample 0.1s, a duży sample TLE przy 5s, co było dla mnie wybitnie zadziwiające. No i sformułowanie w treści faktycznie wskazywało, że te śmiesznostki skryptu run.py nie mają znaczenia na faktyczny mierzony czas, ale nadal ten skrypt powinien wyświetlać jakiś "oficjalnie" zmierzony czas. Teoretycznie mogłem sobie sam dodać jakiegoś clocka do pknsoc.cpp, ale po pierwsze zadanie powinno moim zdaniem zostać tak przygotowane, aby do dostarczonych kodów nie musieć zaglądać i ich modyfikować aby mieć dostępne takie podstawowe funkcjonalności, a po drugie miło by było być pewnym, że dobrze rozumiemy co faktycznie jest mierzone (widząc czas, który raportuje nam nasz czas działania), bo w przypadku wszelakich fuckupów (jak ten, którego ja doświadczyłem) zaczyna się kwestionować, że 2+2=4. Przez brak uruchomień próbnych i niski limit submitów nie mogłem sobie pozwolić na żadne binserczowanie ile razy mój program jest wolniejszy na sprawdarce i jakieś "interaktywne debugowanie" i jedyne z czym mogłem operować to informacja, że "u mnie działa, na sprawdzarce nie i nawet nie mogę sprawdzać działania na tej sprawdzarce", co było mocno frustrujące

5) A owym powodem, dla którego miałem taki rozjazd między czasem lokalnym, a czasem na sprawdzarce było to, że lokalnie odpalałem używając python3 (aka CPython), a na sprawdzarce jest używany pypy i używałem standardowej biblioteki, która w pypy działa 50-100 razy wolniej niż w CPythonie. Jest to oczywiście coś co _idealny zawodnik_ ogarnie, ale nie wszyscy uczestnicy są idealni i są oni tutaj bezpośrednio przez jurorów wsadzani na minę, bo w treści run.py jest uruchamiany przez właśnie python3, podczas gdy na sprawdzarce jest pypy, które jak widać zachowuje się zupełnie inaczej. W treści zdecydowanie powinien się znaleźć pypy, aby wiarygodnie lokalnie symulować to co dzieje się na sprawdzarce. (Tłumacząc dokładniej, komenda w treści wpływa niby tylko na to czego użyjemy do odpalenia run.py, ale potem przez magię run.py nasze rozwiązanie, jeżeli jest w Pythonie, jest odpalane z użyciem tego samego interpretera i gdyby w treści była komenda z pypy, to nie straciłbym kilku godzin życia).

6) Przeglądając pobieżnie run.py zauważyłem, że implementuje on jakieś opcje flag uruchomienia, które robią jakieś rzeczy. Środek bardzo długiego kodu to nie jest miejsce na dokumentację. Miejscem na dokumentację jest dokumentacja. Zawodnicy powinni być poinformowani o takich możliwościach, a nie dowiadywać się o nich przeglądając ten bardzo długi i zaawansowany skrypt. Aczkolwiek zauważyłem, że w treści KOD już zaszła poprawa i było wspomnienie, że takie opcje istnieją i jak się o nich dokładniej dowiedzieć.

Być może były jeszcze jakieś inne problemy, ale na chwilę obecną pamiętam tyle, a i tak się już dużo rozpisałem.

Dodajmy jeszcze do tego, że na kluczowe dla mnie pytanie, od odpowiedzi na które dla mnie dużo zależało, zadane o godzinie 19:00 dostałem odpowiedź dopiero o 23:00. Co prawda odpowiedzi nie były dla mnie szczególnie pomocne, ale na tamten czas wierzyłem, że mierzę się z jakimiś bliżej niezidentyfikowanymi problemami technicznymi, które nie są zależne ode mnie i czekam na zbadanie sprawy przez jurorów, a nawet jeżeli jednak zależą to może jurorzy wykażą się rigczem i zwiększą limit submitów, więc nawet takie mało pomocne odpowiedzi istotnie zmieniły moje podejście (że twierdzą, że wszystko w porządku i nie mam co się łudzić na zwiększony limit submitów i nie mam na co czekać i muszę sobie radzić sam), a po odpowiedzi raczej wnioskuję, że pomiędzy zadaniem mojego pytania a otrzymaniem odpowiedzi nie działy się po stronie jurorów żadne szczególne przemyślenia i równie dobrze mogłem dostać taką samą w minutę.
ad1 - interaktor kompilował program tylko jeśli podałeś ścieżkę do pliku cpp. Jeśli podałeś ścieżkę do exe - tylko wykonywał. Choć zgoda - to zachowanie nie było nigdzie udokumentowane, a plik exe trzeba było samemu znaleźć.

ja od siebie dodam:
7) problemy z działaniem pod Windowsem. (Może po drodze gdzieś to naprawili; po pierwszych zadaniach zniechęciłem się i napisałem własny komunikator, więc przestałem śledzić zmiany). Jeśli program działa tylko pod jednym systemem, to wypadałoby o tym jasno poinformować. A już idealnie - dostarczyć gotowe rozwiązanie dla osób nielinuxowych typu działające środowisko albo chociaż gotowy obraz dockera, do którego wystarczy wrzucić swój kod i odpalić.
Ad ad 1: nie zgadzam się, ja podawałem plik wykonywalny, a run.py zgadywał sobie z niego prawdopodobną nazwę mojego kodu źródłowego i kompilował takowy. Więc dlatego to było takie zaskakujące. Gdybym podawał ścieżkę do kodu to w istocie kompilacja byłaby w pełni spodziewana. Nie mam teraz jak sprawdzić tego zachowania, aczkolwiek wierzę że jakby mój plik wykonywalny miał jakieś rozszerzenie typu .e czy .exe to może zachowanie byłoby inne, ale moje binarki (jak i chyba większości ludzi) nie mają zwyczaju specyfikowac rozszerzenia
ad.1 A skąd niby run.py miałby wiedzieć jak nazywa się flaga do lokalnej kompilacji której używasz. Wystarczyło dodać ją we flagach w run.py / dopisać sobie #define LOCAL na samej górze swojego kodu i było ok. Alternatywnie można było otworzyć std::ofstream do lokalnego pliku i używać jego zamiast cerr. Trzeba uważać by tego nie zasubmitować, ale nie przesadzajmy z trudnością.

Ale oczywiście też mam coś od siebie do dodania.
8) Kod błędu przekazany do funkcji wa(string) w kodsoc.cpp nie jest wyświetlany przez kodrun.py. wa() można dostac za przekazanie inputu, który ma za dużo testów (xd). Nasze programy nie dostają wtedy inputu, co w większości przypadków doprowadzi po prostu do crasha. W efekcie mamy na terminalu informację informację o crashu i lakoniczne "WA (punkty: 0)". Nie zgodzę się z Wojtkiem, kodrun.py nie jest zaawansowany, on jest po prostu bloated funkcjonalnością która nie była nikomu potrzebna/ nie była użyta, np. był werdykt SE który idealnie nadałby się na błąd w testach, ale kodsoc.cpp go nie używał bo po co.

To powiedziawszy oba zadanka interaktywne były fajne i liczę na powtórkę za rok, tylko z lepszym interaktorem :)
W tym roku z dużą przyjemnością pisałem w Ruście. Używałem Cargo, chociażby żeby nie musieć pisać pliczków konfiguracyjnych dla rust-analyzera. Normalnie odpalałem swój program przez `cargo run --release --bin pkn` (w skryptach dodając `--quiet`). W idealnym świecie przekazałbym tę samą komendę do `run.py` i po prostu by to zadziałało. `run.py -- $(which cargo) run --release --bin pkn` też byłoby ok.

`run.py target/release/pkn` nie działa, bo przez brak rozszerzenia `.e` podlega procesowi szukania pliku (swoją drogą `.e` to chyba jakieś rzadkie rozszerzenie, spodziewałbym się raczej `.bin`, ewentualnie `.out` z analogii do `a.out`). Zrobienie symlinku nazwanego `pkn.e` nie pomaga, bo symlink jest rozwiązywany. Finalnie miałem skrypt, który woła Cargo, kopiuje binarkę do nazwy z `.e` i dopiero odpala `run.py`. Nie jest to jakieś straszne, ale musiałem się troszkę wgłębić w to, jak działa skrypt.

Jeszcze jedna sugestia, choć potencjalnie bardzo trudna do spełnienia, jest taka, żeby interaktor i `run.py` były w jednym języku i zlinkowane razem (lub w jednym interpreterze Pythona). To chyba rozwiązałoby wiele problemów ze środowiskiem, w tym mój, o który się jednak mocno postarałem (tworząc sobie z pomocą Niksa środowisko z odpowiednią wersją Rusta, jakoś wrzuciłem sobie przez przypadek do PATHa też GCCa, który był jakoś wybrakowany i nie był w stanie zbudować interaktora; nie inwestygowałem tego za długo, tylko przerobiłem skrypt by korzystał z `/usr/bin/gcc`), ale też mogłoby na przykład pomóc z Windowsem.