DŮLEŽITÉ UPOZORNĚNÍ!
Policie České republiky a šéfcensor Ústavu pro studium totalitních režimů Jaroslav Čvančara varují: citovat jakékoli texty z tohoto blogu způsobuje vážné nebezpečí trestního stíhání! Četba na vlastní nebezpečí!

19. 1. 2014

Retro pro osm bitů

Nejsem nijak výrazným nostalgikem a retro-mániemi většinou netrpím, přesto jsem se dal na Facebooku zviklat Vítem Zvánovcem a vida, jak jiní propadají počítačové retrovlně, podlehl jsem vábení a rozhodl se přispět svými pár bity do díla, když pro nic jiného, tak – řekněme – ze studijních důvodů.

Rozhodnuto, budu tedy emulovat… Což o to, emulovat se dá leccos a lecjak. S rostoucím výkonem procesorů roste obliba emulátorů starších a museálních zařízení fungujících přímo v prohlížeči a psaných v JavaScriptu. Takto se podařilo naemulovat mj. Linux a emulátory spekter a prvních PC s MS-DOSem jsou běžnou věcí.

To pokládám za určitý paradox, protože účelem JavaScriptu rozhodně nemá být dodat uživateli prohlížeče software. K tomu měla být primárně určená Java a javové applety, jenže té jako kdyby dnes už nikdo nevěřil a – další paradox – Java se používá nejvíc tam, kde existuje fixní API a kde by bylo výhodnější psát v C/C++, protože procesorového výkonu rozhodně není nazbyt, tedy v Androidu. Trochu mi to připadá, jako kdybychom výpočty pro předpověď počasí zadávali superpočítači v makrech tabulkového procesoru a rodinný rozpočet počítali v assembleru…

Rozhodl jsem se proto, že budu kanonický a programovat budu v Javě.

Druhou otázkou bylo, který osmibitový počítač zvolit. Zalovil jsem v paměti. První počítač jsem si koupil v r. 1991 a bylo to už PC, tehdy s procesorem 386SX na 16 MHz a jedním megabytem paměti; vlastníkem osmibitového počítače jsem nikdy nebyl. Z osmibitů jsem intimně poznal několik strojů: o panictví jsem přišel cca v r. 1983 na Video Genie System (klonu TRS-80), později jsem používal československé výrobky SP-830 (?), PMD-85 a IQ-150/151, a jako poslední osmibit jsem měl jakýsi polský stroj s CP/M. Z něj jsem přešel na PC kompatibilní monstrum Tesla PP-06, což byl pozoruhodný výtvor československé improvisační školy: protože např. nebyly k mání procesory Intel 8088, pépéčko používalo 8086 (takže bylo vlastně rychlejší než na stejné frekvenci taktované IBM PC). A, překvapivě, ač to bylo obrovské a vážilo to snad metrák, jako jediný počítač, se kterým jsem do té doby přišel do styku, měl jakž-takž vyřešené chlazení a nepřehříval se.

Retrovášeň jsem se rozhodl ukojit pomocí PMD-85, což byl počítač, který mne svou notorickou nespolehlivostí a frapantně chybným návrhem přivedl k hardwaru: tyto počítače bylo nutné nejen opravovat, ale uživatel je musel i nejrůznějšími způsoby modovat, aby vykompensoval konstrukční vady.

Tedy dobrá, javový emulátor PMD-85, vzhůru do díla.

Začal jsem emulací procesoru. Péemdéčko běží na procesoru 8080A, tzn. na tom nejjednodušším, co se dá emulovat: emulace byla hotová za večer, a druhý den mi virtuální procesor prošel testem 8080/8085 CPU Excerciser (Sunhillow), který kontroluje i všechny idiosynkratické hodnoty flagů.

Emulátor CPU jsem si napsal nejprve v C, pak jsem ho ze zvědavosti přepsal do Pythonu a nakonec do Javy, dosti nezpůsobně u toho kleje, maskuje a castuje, proklínaje společnost Sun za to, že neimplementovala unsigned typy (Microsoft tuto chybu, tuším, v C# nezopakoval).

Překvapilo mě, jak je Java proti C pomalá. Na svém nikoli špičkovém počítači (optimalisovaném na tichost provozu, nikoli na procesorový výkon) mi celý Sunhillow test v C trvá 18 sekund, kdežto v Javě to samé zabere 9 minut. Buď neumím v Javě dobře a efektivně programovat, anebo jsou propagační řeči  P.R.-manů Oraclu o tom, jak se Java blíží rychlostí k C/C++, od reality stále na hony vzdáleny. V Pythonu, podle očekávání, trval test přes hodinu, ale jeho doménou podobné výpočty nejsou (ano, přiznávám, přesto mne to jako zamilovaného pythonistu poněkud ranilo, dva řády je prostě moc…).

Zbývá maličkost, implementovat hardware počítače, což by neměla být s ohledem na jeho jednoduchost práce na příliš dlouhou dobu. Výstup na display a emulace magnetofonu jsou jasné, pro vstup chci použít nezávisle myšovatelnou javovou klávesničku, jejíž grafický návrh má arci, jak patrno, k dokonalosti dosud daleko.

Jsem tedy zvědav, co nakonec vytvořím. Za dosud nejlepší pokládám emulátor slovenského RM-Teamu, uvidíme, jak se s tímto benchmarkem poperu; o výsledku budu samozřejmě na tomto místě informovat.

22 komentářů :

  1. Jste dobry ze jste to tak rychle zvladnul. Ale s tim pomalostnim faktorem 30 Javy proti C jste to podelal... dal bych maximalne faktor 3 pro extremni low-level veci, tady tak nejvys 1.2. Nechcete se podelit o nejaky pomaly kus kodu, tady nebo treba na stackoverflow.com? Kdyby byla Java tak pomala jak pisete, tak by se s ni nikdo nezahazoval, tak dobry PR nikdo neumi.

    OdpovědětVymazat
    Odpovědi
    1. Příklad:

      case 0xe4: /* CPO */
      if (PE) {
      PC += 3;
      cyc += 11;
      } else {
      tb = mem[++PC & 0xffff];
      tw = tb + (mem[++PC & 0xffff] << 8);
      PC++;
      SP--;
      if (wr_mask[SP])
      mem[SP] = PCH;
      SP--;
      if (wr_mask[SP])
      mem[SP] = PCL;
      PC = tw;
      cyc += 17;
      }
      break;

      To samé v Javě:

      case 0xc4: // CNZ
      if (ZFSET()) {
      PC = (PC + 3) & 0xffff;
      cyc += 11;
      } else {
      PC = (PC + 1) & 0xffff;
      tb = mem[PC] & 0xff;
      PC = (PC + 1) & 0xffff;
      tw = tb + ((mem[PC] & 0xff) << 8);
      PC = (PC + 1) & 0xffff;
      SP = (SP - 1) & 0xffff;
      if (wr_mask[SP])
      mem[SP] = (byte)(PC >> 8);
      SP = (SP - 1) & 0xffff;
      if (wr_mask[SP])
      mem[SP] = (byte)(PC & 0xff);
      PC = tw;
      cyc += 17;
      }
      break;

      Vymazat
    2. Vidím, že to není úplně to samé, sáhl jsem o case jinam (ale je to výpočetně téměř identické, samozřejmě); asi je načase jít spát.

      Vymazat
    3. Napadá mě, jestli Java neprochází ten switch case po casu, to by pak bylo tragické (a pomohlo by přepsat to do tabulky). Podívám se do bytecodu.

      Vymazat
    4. To se resi skokovou tabulkou, to je v poradku. Predpokladam ale ze jich je asi milion - v tom bude problem. JIT je dobry (nekdy vyborny, nekdy min) ale pro metody rozumne delky, kdyz je to par set radku tak se na to proste vykasle. Takze spaghetti-code ma smulu, normalne pomaha extrahovat metody, coz je presne opak o cem pisete niz. JIT umi inlinovat, takze volani metody se neni treba bat (ve vasem pripade ale neoptimalizoval nejpis vubec nic).

      Bytecode je irreleventni, na cem zalezi dostanete pres
      java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly jmeno_programu
      Je to ale o dost min citelny nez gcc -S.

      Vymazat
    5. Ano, bylo to tak, rozsekání do method zlepšilo výsledek na minutu pět sekund. Optimalisace byla zřejmě příliš dlouhým kodem paralysována. Ještě zkusím předělat switch na array method, co to udělá s rychlostí.

      Vymazat
    6. Tabulka zhoršila výsledek o cca 20 sekund; je to logické, instrukce musejí být implementovány jako podtřídy s interfacem a to má nějakou režii. Proto zůstávám u switche se závěrem 1m3s, tj. cca 3,5× víc než v C.

      Vymazat
  2. na uvedem prikladu by Java nemela prohrat ani o kousek, natoz o takovy hrozivy rozdil. Problem bude ukryt jinde. Switch by mohl byt problem a nebo je problem ukryt uplne jinde.

    OdpovědětVymazat
    Odpovědi
    1. Switch je zkompilován správně do skokové tabulky, a když jsem odstranil volání method tam, kde má C makra, ušetřil jsem cca 30 sekund. Vhodný čas začít pátrat po nějakém javovém profileru…

      Vymazat
    2. Naprosto dostacujici profiler (zdarma): http://visualvm.java.net/

      Vymazat
    3. Díky, to už jsem překonal rozdělením do anonymních instancí interfacu. Výsledný čas je 1m29s, což pro účely emulace osmibitů bohatě stačí.

      Vymazat
  3. A mimochodem, program v C vytváří emulovanou frekvenci 8080 cca 1,28 GHz, což je vzhledem ke skutečné frekvenci procesoru 2,67 GHz docela brutální tempo.

    OdpovědětVymazat
  4. Trochu mě mrzí, že jsem nikdy nepoznal Ondru.

    OdpovědětVymazat
  5. Tak jsem to nakonec udělal takto:

    interface Executable {
    public abstract int exec();
    }

    class Opcode {
    String mnemo, par;
    int length;
    Executable exec;

    Opcode(String mnemo, String par, int length, Executable exec) {
    this.mnemo = mnemo;
    this.par = par;
    this.length = length;
    this.exec = exec;
    };
    }

    public final Opcode [] opcodes = new Opcode [] {

    // RZ
    new Opcode("RZ", "", 1, new Executable() {
    public int exec() {
    if (ZFSET()) {
    int tb = mem[SP] & 0xff;
    SP = (SP + 1) & 0xffff;
    PC = tb + ((mem[SP] & 0xff) << 8);
    SP = (SP + 1) & 0xffff;
    return 11;
    } else {
    PC = (PC + 1) & 0xffff;
    return 5;
    }
    }
    }
    ),

    cyc += opcodes[opc].exec.exec();


    Nevěděl jsem, že se dá takhle instancovat interface, ale z nějakého důvodu to funguje. Java je prostě divná…

    OdpovědětVymazat
    Odpovědi
    1. ad: Nevěděl jsem, že se dá takhle instancovat interface, ale z nějakého důvodu to funguje. Java je prostě divná…

      Ale kdepak divna. To co jste udelal je anonymni trida. Proste nepojmenovana implementace interfacu Executable. Ja nejsem zadny Java propagator a nehajim ten jazyk az do roztrhani tela, ale kdyz se o nem takto vyjadrujete, tak vezte, ze se divnym stavate vy a ne ten jazyk, protoze jen poukazujete na to, ze mu (zatim) nerozumite.

      Vymazat
    2. To uznávám, s Javou teprve začínám. Anonymní třídy nicméně znám, jen jsem nevěděl, že je možné vytvořit instanci z interfacu bez třídy. Intuitivně bych očekával, že musím k interfacu vytvořit (pojmenovanou) třídu a až z té odvozovat anonymní podtřídy.

      Vymazat
    3. Je pravda ze anonymni trida se vlastne trochu jinak dela z interfacu a jinak ze tridy, ale nakonec je to logicke. Tady se jako super vezme Object. Kdyby tomu bylo jinak, bylo by to zbytecne ukecane - a Java je ukecana az az (closures budou/jsou v JDK8).

      Vymazat
    4. O tom jsem uvažoval, když jsem programoval hardwarové řešení pinů v periferiích, ale vyřešil jsem to tak, že pin je anonymní podtřída integrovaného obvodu (nebo obecně zařízení), která si svou "roli" v něm pamatuje v instančních proměnných. Není to tak elegantní, ale funguje to, a do fungujícího kodu není rozumné zbytečně vrtat.

      Vymazat
  6. Dovolil bych si jako příznivec Javy reagovat k výše uvedenému - takže jen velmi stručně.

    "Přispělo k němu i to, že jako mnoho jiných z mé generace nemám příliš
    dobrý vztah k objektově orientovanému programování
    (OOP), a pokládám ho za modní výstřelek konce 80. let,
    který z nějakého důvodu opomněl odeznít... jsa přesvědčen,
    že neobjektově by se to samé dalo napsat mnohonásobně jednodušeji a efektivněji."


    Psal jsem něco v Pascalu (včetně objektového), C, C++, php, minimum v C#.
    Když jsem nedávno psal něco po delší době pro AVR v C, tak to pro mě byl po letech psaní v Javě
    velký nezvyk a objekty mi hodně chyběly.
    Vybavuji si z FELu ukázky kódu v C++ a tomu odpovídající C kódy (některé překladače nejdřív vytvořily z C++ kód v C) a musím říct, že kód v C++ byl přehlednější a kratší.
    Myslím, že Java umožňuje psát programy velmi efektivně (z hlediska tvorby kódu), přehledně a jednoduše; např. tvorba okna v C win api a v Javě děděním z JFrame je myslím nesrovnatelná).

    Z ukázky vašeho kódu bych tipoval, že jde o přepis z C,
    kde 4 parametr byl ukazatel na metodu, objektově by myslím měla třída Opcode
    potomky pro jednotlivé typy instrukcí s metodou execute různou pro různé
    instrukce, mimo jiné by odpadl konstruktor se 4 parametry (o počtu parametrů se pěkně píše
    např. v knize Čistý kód/R.C. Martin - ideální je metoda bez parametrů, monadická příp. dyadická)
    a navíc je možné, že by část kódu v metodě exec mohla přejít do předka.

    A ještě k anonymní instanci rozhraní; to je věc, která je dost užitečná a často používaná.
    Mimo jiné umožňuje elegantně obejít absenci ukazatele na metody v C, nebo delegáty v C# -
    ukázka použití v Javě je např. v Návrhové vzory/Pecinovský u vzoru Command.

    A velmi často se používá (jako tzv. callback), pokud se mění kód obklopený stejnými úseky kódu, klasicky
    např. getDBconnection-neco XXX-catch blok-finally-close nebo obdobně při použití transakce:

    TransactionCallbackWithoutResult action = new TransactionCallbackWithoutResult() {

    @Override
    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    transactionalUpdate(record);
    }
    };

    getRequiredPropagationTransactionTemplate().execute(action);

    toto je velmi často používaná konstrukce (zde Spring a často v Hibernate).
    Nemohl jsem si odpustit alespoň velmi stručný komentář k OOP a Javě. :-)



    OdpovědětVymazat
    Odpovědi
    1. Díky. Mezitím jsem postoupil v Javě o něco dál (minimálně o 16 tisíc řádků emulátoru PMI-80, který se mi rozrostl o spoustu periferií, které jsem si prostě nemohl odpustit do základu přidat). S objekty jsem se smířil, někde se hodí (např. když jsem jako jednu z periferií kompletně naemuloval terminál DEC VT220), někde mi jejich použití připadá umělé, ale naučil jsem se s nimi žít, aniž bych je pokládal za nutné zlo.

      Momentálně mi asi nejvíc dvojkolejnost polí a primitivních/normálních typů, která má daleko k eleganci pythonských konstrukcí. Boxing a unboxing si mi zdá nedomyšlený. Příkladmo když parsuji vstupní data do VT220, vytvářím si pracovní pole neukončených ESC-, CSI- a DCS- sekvencí, na což se mi hodí ArrayList, takže se nestarám o přetečení indexu. Ale když potřebuji data z těchto listů vložit do switche jako char, musím použít konstrukci (např.) (char)(int)esc.get(0), protože Java neumí zkonvertovat Integer na char žádným jiným způsobem. Proboha, proč? Vždyť je to odporné a znepřehledňuje to kod!

      Mít pro to samé dva různé typy, např. Long a long, to je idiosynkrasie, kterou nemá snad žádný jiný programovací jazyk, a její účelnost je rovna nule.

      Vymazat
    2. "S objekty jsem se smířil...někde mi jejich použití připadá umělé"; pohybuji se v oblasti J2EE tj. Javy na serverech (to je myslím ta oblast, kde má Java svoje místo; applety se občas můžou hodit, ale je to spíš okrajová záležitost - volání metody, jejíž jedna část se provede na linux serveru, jiná na windows serveru, přičemž se použije volání přes webovou službu nebo JMS aniž by to musel člověk nějak řešit - to je myslím přednost Javy, dále pak standardizace JSR - pominu zobrazení číslic na vašem linuxu - mimochodem; použil jste AWT nebo Swing?) a použití objektů mi nepřipadá umělé vůbec nikde.

      K Integer vs int; vy máte trochu specifickou úlohu, ale já myslím, že každý typ se používá na něco jiného. Můžete všude použít Integer, ale pak se můžete dostat rychlostí někam k Pythonu. Když jsem kdysi zkoušel smyčku s Integer a int, byla smyčka s Integer cca 10x pomalejší; tj. na výpočty s čísly, práci s maticemi, smyčky apod. int, na data spíše Integer.
      Např. Matlab měl (asi má pořád) pod kapotou Javu a když se hodně zadařilo, vypadl Java stacktrace s hláškou že "int XXX".
      Naopak někdy chcete vědět, jestli je hodnota vůbec nastavená (Integer hodnota je null);např. persistentní objekt Hibernate s klíčem Integer jako identitou prostě s typem int nefunguje.
      Potřebujete tak často ty přetypování? A nejdou ty switche nahradit polymorfismem (jak je to preferováno v OOP[tj. switch jen ve factory metodach] - osobně si nepamatuju, kdy jsem naposled použil switch - ale jak jsme psal; řeším trochu jiné typy úloh a rychlost provádění programu nehraje roli, tj. abych řešil, jestli switch nebo polymorfismus).

      Vymazat
    3. pominu zobrazení číslic na vašem linuxu

      Jakých číslic? Tomu nerozumím, téměř vše, co vidíte, jsou bitmapy.

      mimochodem; použil jste AWT nebo Swing?

      Swing.

      Potřebujete tak často ty přetypování? A nejdou ty switche nahradit polymorfismem

      Nepotřebuji, kdybych potřeboval, napsal bych si na to statickou konversní methodu nebo subclassoval ArrayList.

      Obecně mám objekty za samoúčelné všude tam, kde emuluji historický algorithmus. Příkladmo VT220 v sobě měl jednu 8051 a zpracování escape sekvencí řešil jako stavový automat (bohužel, jeho firmware není veřejně dostupný, pokud by byl, celá emulace by byla hračka). Když nad tento algorithmus začnu roubovat něco "moderního", jen si přidělám práci a s velkou pravděpodobností vytvořím něco, co nebude s originálem 100% kompatibilní. Když emuluji historický firmware, musím při tom i "historicky" myslet.

      Vymazat

Kursiva: <i>text</i>
Tučně (když už to musí být…): <b>text</b>
Odkaz: <a href = "http://adresa">název odkazu</a>, tedy <a href = ""></a>

Poznámka: Komentáře mohou přidávat pouze členové tohoto blogu.