• Memori,  Omong Kosong,  Programming,  Teknologi

    Ternyata…

    Alkisah, pada jaman dahulu kala, saya pernah kepikiran untuk bikin reporting server, dimana semua report yg direquest akan masuk ke daftar antrian dan akan di eksekusi sesuai prioritas. Di servernya akan ada daftar request dan statusnya. Data yang dihasilkan untuk setiap request report akan disimpan. Tiap report yg dicetak akan ada “signature” atau kode tertentu yang mengarah ke id request report. Itu sekitar tahun 2012.

    Tahun ini baru saya tau kalau ternyata udah ada yang bikin kaya gitu.

  • Komputer,  Programming

    [Copy] Need for Speed

    Dicopy dari blog saya yg di blog.sedjat1.dx.am

    Sesudah bikin blog, langkah selanjutnya adalah mendaftarkan blog ini untuk di index di search engine. Maka saya langsung meluncur ke dmoz.org. Tapi ternyata dmoz.org udah ditutup, yg ada tinggal mirrornya aja dan saya gak dapetin cara untuk nambahin blog saya dalam salah satu direktorinya.

    Akhirnya saya nyari direktori internet lain yg bisa nampung blog saya ini. Akhirnya saya dapet daftar dari “Free Web Directories” yg ternyata gak free2 amat. Saya penasaran memangnya apa sih yg bikin web direktori itu gak free, datanya sebesar apa yg dibutuhkan untuk data web direktori itu?

    Saya berburu data. Dengan bersenjatakan google, akhirnya saya dapet mangsa juga: Dataverse. Dapet data dmoz.org per 12 Juni 2016. Ada 3 file yg bisa didownload, dan yang saya proses duluan yg tab delimited csv.

    Sekilas lihat datanya, saya langsung bikin table di MySQL, 3 table, standar aja (id primary key autonumber) 1 table struktur folder, 1 table daftar domain, 1 table mapping domain dan folder. Table “directory” isinya ID, NamaFolder, Parent_ID. Table “domain” isinya ID, NamaDomain. Table “domain_map” isinya ID, Domain_ID, Folder_ID.

    Tab delimited file cuma ada 2 field, domain dan array of path yg nantinya akan dimapping ke table “directory”. Program dibuat menggunakan PHP (bukan yg dibuka pake browser, tapi yg di jalanin lewat command prompt). Awalnya program berjalan lancar, tanpa hambatan, cepat. Tapi lama2 makin kelihatan lambat. Karena dlm scriptnya saya selalu memeriksa keberadaan data sebelum di insert, maka akhirnya saya buat index yang sesuai untuk semua table.

    Program kelihatan lebih cepat jalannya, tapi itu gak bertahan lama, gejala melambat langsung terlihat. Akhirnya program saya ubah lagi, terutama dibagian akses database. Yg sebelumnya pake fungsi, akhirnya saya buat PDO prepared statement untuk tiap query, jadi pas looping data cukup memanggil method execute dengan parameter yang sesuai. Ada kemajuan kecepatan walaupun tidak signifikan.

    Saya sadar sebenarnya index mempercepat perintah select, tapi memperlambat perintah insert. Dan semakin datanya banyak tambah beban juga. Akhirnya saya memutuskan untuk memakai cache data berupa array. Data table saya load ke dalam array, jadi pengecekan data existing dan lookup id saya lakukan pada array tidak mengakses database sama sekali, akses database hanya dilakukan pada saat insert data. Ini membuat database tidak memerlukan index (selain primary key).

    Saat scriptnya dijalankan, error: out of memory. Saya lupa ngeset maksimum memorynya. Running kedua kali… Wussss… langsung terlihat perbedaannya. Memang startupnya lama karena memuat semua data ke array, tapi sesudah itu data dengan amat sangat lancar dibaca dan distore ke database.

    Jadi seperti yang pernah saya tulis di entry blog sebelumnya: Selalu cari jalan tercepat, jangan puas hanya dengan hasil yang benar.

    Update:

    Diujung (row ke 2.3jt dari 2.4jt), memory yang dibutuhin udah ngelewatin batesan yg saya tentuin (1.6GB). Karena keterbatasan sisa RAM yg saya punya (2GB), akhirnya saya pake metode hybrid, yg di load ke array adalah data yang paling banyak di select, yaitu data directory, sisanya tetap akses ke database (dengan index). Hasilnya walaupun gak secepet yg full array, masih lebih cepat dari pada full db.

    MySql Table Information

    Console View and Source Text

    Speed is everything, but I can’t bite off more than I can chew.

  • Programming

    Game of Life

    Kemaren waktu nyari info tentang “map”nya Java di stackoverflow.com, di samping kanan websitenya ada bagian “Hot Network Questions” yg isinya adalah pertanyaan2 dari berbagai bidang (dan dari berbagai website), ada yg dari superuser.com, math.stackexchange.com, codegolf.stackexchange.com, english.stackexchange.com, codereview.stackexchange.com, dll (detailnya liat langsung aja di link ‘Hot Network Questions’ di atas).

    Nah, dari code review, ada yg nanya tentang Conway’s Game of Life. Penasaran, akhirnya malah bikin sendiri deh, pake javascript aja yg gampang. Informasinya saya dapet dari wikipedia. Dunia dari game of life adalah sel kotak yang hidup dalam grid dua dimensi yang tak berhingga luasnya. Tiap sel dari grid cuma punya salah satu status, yaitu hidup atau mati. Tiap kali perubahan memiliki hukum seperti ini:

    1. Sel hidup yang punya tetangga hidup kurang dari 2 akan menjadi mati.
    2. Sel hidup yang punya tetangga hidup 2 atau 3 tetap akan hidup.
    3. Sel hidup yang punya tetangga hidup lebih dari 3 akan mati.
    4. Sel yang mati tapi punya 3 tetangga hidup akan menjadi sel hidup.

    Langsung aja, ini source-code nya:

    [code lang=”HTML” title=”Source-code: conways.html”]
    <html>
    <head>
    <title>Conway’s Game of Life</title>
    <style type="text/css">
    table.cgol { border-collapse:collapse; border: 1px solid #333; border-spacing: 0px; }
    table.cgol td { text-align:center; width:10px; height:10px; border: 1px solid #333; overflow: hidden; }
    </style>
    <script type="text/javascript" src="conways.js"></script>
    <script type="text/javascript">
    var gol;
    var lop;
    function $(id) { return document.getElementById(id); }
    function getRandomInt(min, max) { return Math.floor(Math.random()*(max-min+1)+min); }
    function isOdd(num) { return (num % 2) == 1; }

    function draw() { $(‘output’).innerHTML = gol.draw(); $(‘stp’).innerHTML = gol.steps; }
    function doCreate() {
    if (typeof lop != "undefined") clearInterval(lop);
    clearInterval(lop);
    gol = new CGOL($(‘we’).value,$(‘ha’).value,"gol");
    draw();
    }
    function doRandom() { gol.initRandom(); draw(); }
    function doStep() { gol.step(); draw(); }
    function doStepClick() { if (typeof lop != "undefined") clearInterval(lop); clearInterval(lop); doStep(); }
    function doLoop() { if (typeof lop != "undefined") clearInterval(lop); lop = setInterval(doStep, 150); }
    </script>
    </head>
    <body>
    <h2>Conway’s Game of Life</h2>
    <div>
    Width <input type="text" id="we" value="50">
    Height <input type="text" id="ha" value="50">
    <input type="button" value="Create" onclick="doCreate()">
    <input type="button" value="Random" onclick="doRandom()">
    <input type="button" value="Step" onclick="doStepClick()">
    <input type="button" value="Auto" onclick="doLoop()">
    </div>
    <br>
    Steps : <span id="stp"></span>
    <br>
    <div id="output"></div>
    </body>
    </html>
    [/code]

    [code lang=”Javascript” title=”Source-code: conways.js”]
    function CGOL (wid, hig, nam) {
    this.width = wid;
    this.height = hig;
    this.cells = [];
    this.varname = nam; // little hack for board designer
    this.steps = 0;

    this.init();
    }

    CGOL.prototype.alive = function(x,y) { return this.cells[(this.width*y)+x]; }
    CGOL.prototype.toggle = function(x,y) { this.cells[(this.width*y)+x] = !this.alive(x,y); }

    CGOL.prototype.countLiveNeighboursSimple = function(x,y) {
    /*
    * The simplest strategy is simply to assume that every cell outside the array
    * is dead. This is easy to program, but leads to inaccurate results when the
    * active area crosses the boundary.
    */
    var live = 0;
    var atas = false;
    var bawa = false;
    var kiri = false;
    var kana = false;
    if (x>0) kiri = true;
    if (x<this.width-1) kana = true;
    if (y>0) atas = true;
    if (y<this.height-1) bawa = true;

    if (kiri) {
    if (this.alive(x-1,y)) live++;
    if (atas) { if (this.alive(x-1,y-1)) live++; }
    if (bawa) { if (this.alive(x-1,y+1)) live++; }
    }
    if (kana) {
    if (this.alive(x+1,y)) live++;
    if (atas) { if (this.alive(x+1,y-1)) live++; }
    if (bawa) { if (this.alive(x+1,y+1)) live++; }
    }
    if (atas) { if (this.alive(x,y-1)) live++; }
    if (bawa) { if (this.alive(x,y+1)) live++; }
    return live;
    }

    CGOL.prototype.countLiveNeighbours = function(x,y) {
    /*
    * A more sophisticated trick is to consider the left and right edges of the
    * field to be stitched together, and the top and bottom edges also, yielding
    * a toroidal array. The result is that active areas that move across a field
    * edge reappear at the opposite edge. Inaccuracy can still result if the
    * pattern grows too large, but at least there are no pathological edge
    * effects.
    */
    var live = 0;
    var atas = false;
    var bawa = false;
    var kiri = false;
    var kana = false;
    if (x>0) kiri = true;
    if (x<this.width-1) kana = true;
    if (y>0) atas = true;
    if (y<this.height-1) bawa = true;

    if (kiri) {
    if (this.alive(x-1,y)) live++;
    if (atas) { if (this.alive(x-1,y-1)) live++; } else { if (this.alive(x-1,this.height-1)) live++; }
    if (bawa) { if (this.alive(x-1,y+1)) live++; } else { if (this.alive(x-1,0)) live++; }
    } else {
    if (this.alive(this.width-1,y)) live++;
    if (atas) { if (this.alive(this.width-1,y-1)) live++; } else { if (this.alive(this.width-1,this.height-1)) live++; }
    if (bawa) { if (this.alive(this.width-1,y+1)) live++; } else { if (this.alive(this.width-1,0)) live++; }
    }
    if (kana) {
    if (this.alive(x+1,y)) live++;
    if (atas) { if (this.alive(x+1,y-1)) live++; } else { if (this.alive(x+1,this.height-1)) live++; }
    if (bawa) { if (this.alive(x+1,y+1)) live++; } else { if (this.alive(x+1,0)) live++; }
    } else {
    if (this.alive(0,y)) live++;
    if (atas) { if (this.alive(0,y-1)) live++; } else { if (this.alive(0,this.height-1)) live++; }
    if (bawa) { if (this.alive(0,y+1)) live++; } else { if (this.alive(0,0)) live++; }
    }
    if (atas) { if (this.alive(x,y-1)) live++; } else { if (this.alive(x,this.height-1)) live++; }
    if (bawa) { if (this.alive(x,y+1)) live++; } else { if (this.alive(x,0)) live++; }
    return live;
    }

    CGOL.prototype.step = function() {
    var newcell = [];
    for (var y=0; y<this.height; y++) {
    for (var x=0; x<this.width; x++) {
    var t = this.countLiveNeighbours(x,y);
    newcell.push(this.alive(x,y));
    var i = newcell.length-1;
    //Any live cell with fewer than two live neighbours dies, as if caused by under-population.
    if (this.alive(x,y) && (t<2)) newcell[i]=false;
    //Any live cell with two or three live neighbours lives on to the next generation.
    if (this.alive(x,y) && t>=2 && t<=3) newcell[i]=true;
    //Any live cell with more than three live neighbours dies, as if by overcrowding.
    if (t>3) newcell[i]=false;
    //Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
    if (!this.alive(x,y) && t==3) newcell[i]=true;
    }
    }
    this.cells = [];
    for (var z=0; z<newcell.length; z++) {
    this.cells.push(newcell[z]);
    }
    this.steps++;
    }

    CGOL.prototype.init = function() {
    this.cells = [];
    for (var x=0; x<this.width*this.height; x++) {
    this.cells.push(false);
    }
    this.steps = 0;
    }

    CGOL.prototype.initRandom = function() {
    var min = 0;
    var max = 9;
    this.cells = [];
    for (var x=0; x<this.width*this.height; x++) {
    var c = (((Math.floor(Math.random() * (max – min + 1) + min)) % 4) == 1);
    this.cells.push(c);
    }
    this.steps = 0;
    }

    CGOL.prototype.draw = function() {
    var tbl = "<table class=’cgol’>";
    for (var y=0; y<this.height; y++) {
    tbl += "<tr>";
    for (var x=0; x<this.width; x++) {
    var warna = "#000";
    if (this.alive(x,y)) warna = "#0f0";
    tbl += "<td style=’background-color:" + warna + "; ‘ "+
    "onclick=’"+this.varname+".toggle("+x+","+y+"); draw();’></td>";
    }
    tbl += "</tr>";
    }
    tbl += "</table>";
    return tbl;
    }
    [/code]

    Seperti keterangan pada wikipedia, ada metode paling simple untuk menghitung jumlah “tetangga hidup” yaitu dengan menganggap sel diluar array adalah sel mati. Metode ini dianggap sangat tidak akurat. Di conways.js, saya sertakan metode ini dengan nama “countLiveNeighboursSimple”.

    Lalu ada metode lainnya yaitu yang menganggap bahwa bagian kiri dan kanan grid adalah menyatu, begitu pula bagian atas dan bawah. Kalau di visualisasikan, akan terbentuk torodial. Ini yang jadi default cara menghitung “tetangga hidup” dalam conways.js.

  • Programming

    Optimasi Query

    Beberapa hari lalu saya diminta untuk membuat ulang program penghitung harga rata-rata pada tiap akhir hari perdagangan di back-office. Program sebelumnya selalu menghitung pembelian terlebih dahulu baru mengurangi balance saat penjualan, tentu saja hasilnya akan berbeda jika perhitungan dilakukan menurut waktu terjadinya transaksi.

    Yang pertama kali saya lakukan adalah membuat Stored Procedure yang kerjanya mengambil data transaksi, lalu mengupdate/insert (data disimpan per tanggal) tabel saldo yg berisi Saldo, AvgPrice, dll, terurut menurut waktu terjadinya transaksi. Stored procedure yang saya buat berjalan lambat, karena pada tiap transaksi stored procedure akan menulis ke table yang berarti akan ada aktivitas harddisk untuk menulis, dan dalam 1 hari bisa ada ribuan (antara 2 sampai 4 ribu) frekuensi transaksi.

    Stored procedure saya ubah dengan menambahkan variabel dengan tipe “table”, saya melakukan semua insert ke variabel tabel, lalu di akhir proses saya lakukan bulk insert ke tabel aslinya. Hasilnya stored procedure berjalan 10x lebih cepat.

    Table temporary di memory memang cepat, tapi ada kasus lain yang ternyata kebalikannya. Saya punya 1 table yang akan saya bulk update berdasarkan sebuah multi-statement table-valued function (fungsi yang outputnya berupa table). Begitu saya coba jalankan, query update itu berjalan lebih dari 13 menit. Googling sebentar dapet informasi dari MSDN :

    Joining to a multistatement table valued function in a FROM clause is possible, but can give poor performance. SQL Server is unable to use all the optimized techniques against some statements that can be included in a multistatement function, resulting in a suboptimal query plan. To obtain the best possible performance, whenever possible use joins between base tables instead of functions.

    Saya ubah lagi querynya dengan menyimpan terlebih dahulu hasil fungsi ke tabel fisik dengan menggunakan select into, lalu melakukan bulk-update dan terakhir menghapus kembali tabel penyimpanan sementara itu. Hasilnya query berjalan 39 detik untuk mengupdate 54.827 row data.

    Selalu cari jalan tercepat, jangan puas hanya dengan hasil yang benar.

  • Programming,  Teori

    Bikin Program Pencatat Koleksi

    Sebagai seorang pencinta buku, sudah tentu buku yang saya punya cukup banyak, dan pencatatan menjadi cukup penting. Selain mengoleksi buku, saya juga punya koleksi koin dan koleksi film. Sudah lama saya mencari program yang tugasnya hanya mencatat benda koleksi seseorang, tapi tidak pernah dapat yang benar2 pamungkas. Karena saya memprioritaskan satu program dengan banyak tipe koleksi, berarti program itu harus bisa mengelola data koleksi buku dan film, padahal field yang digunakan dalam dua koleksi itu sangat jauh berbeda (apalagi kalau koleksi koin).

    Sesudah istri saya meminta saya untuk mendokumentasikan seluruh tas miliknya (istri saya senang mengoleksi tas), saya jadi berpikir untuk bikin sendiri program pengelola koleksi itu. Akhirnya tercetus sebuah ide dalam benak saya untuk membuat koleksi dengan field dinamis, user bisa mendefinisikan sendiri field2 apa saja yang mau disimpan. Atau dalam istilah OOP, si user bisa mendefinisikan property apa saja yang terdapat dalam sebuah objek koleksi. Dan untuk mengakomodir user2 malas seperti saya, bisa disiapkan template untuk berbagai macam koleksi yang bisa diedit.

    Karena koleksi semacem ini biasanya cuma dipake oleh 1 orang, maka yang paling gampang pake SQLite.

    Ini dia struktur table nya:

    [table]KOLEKSI[attr colspan=”2″]
    ID,Auto Number
    Nama,Nama Koleksi
    Catatan,”Catatan koleksi (tanggal dimulai, dll)”
    BarisAkhir,Nomor baris yang terakhir dipakai (pada table BARIS)
    [/table]

    [table]KOLOM[attr colspan=”2″]
    ID,Auto Number
    Koleksi_ID,Nomor ID dari table KOLEKSI
    Nama,Nama field
    Tipe,”Tipe data field (string, numeric, date)”
    Urutan,Urutan posisi field
    Referensi,Referensi nilai. Untuk tipe field yang isinya sudah ditentukan dari daftar
    [/table]

    [table]BARIS[attr colspan=”2″]
    ID,Auto Number
    Koleksi_ID,Nomor ID dari table KOLEKSI
    Kolom_ID,Nomor ID dari table KOLOM
    Nilai,Isi field
    NoBaris,”Nomor urut data (tidak boleh dobel, sekuen bisa terputus apabila ada penghapusan data)”
    [/table]

    [table]LAMPIRAN[attr colspan=”2″]
    ID,Auto Number
    Koleksi_ID,Nomor ID dari table KOLEKSI
    NoBaris,Nomor urut data. Satu nomor urut bisa mempunyai lebih dari satu lampiran
    NamaFile,Nama file lampiran
    Berkas,Isi file lampiran
    [/table]

    Contoh datanya kira2 seperti ini:

    [table caption=”KOLEKSI”]ID,Nama,Catatan,BarisAkhir
    1,Koleksi Tas,-,2
    [/table]

    [table caption=”KOLOM”]ID,Koleksi_ID,Nama,Tipe,Urutan,Referensi
    1,1,Merk,string,1,Charles & Keith|Gucci|Channel
    2,1,Warna,string,2,
    3,1,Harga,numeric,3,
    4,1,Tanggal beli,date,4,
    [/table]

    [table caption=”BARIS”]ID,Koleksi_ID,Kolom_ID,Nilai,NoBaris
    1,1,1,Charles & Keith,1
    2,1,2,Hijau Pupus,1
    3,1,3,630000,1
    4,1,4,2011-03-24,1
    5,1,1,Gucci,2
    6,1,2,Merah,2
    7,1,3,2100000,2
    8,1,4,2012-01-19,2
    [/table]

    [table caption=”LAMPIRAN”]ID,Koleksi_ID,NoBaris,NamaFile,Berkas
    1,1,1,foto-1.jpg,–blob–
    2,1,2,tas.png,–blob–
    [/table]

    Nantinya akan ditampilkan seperti ini:

    [table colalign=”center|left|left|right|center”]No,Merk,Warna,Harga,Tanggal beli
    1,Charles & Keith,Hijau Pupus,630.000,2011-03-24
    2,Gucci,Merah,2.100.000,2012-01-19
    [/table]

    Jadi deh program pencatat koleksi yang dinamis cuma pake 4 table. Tinggal pinter2 aja nge-query. Kalau perlu bikin view untuk setiap koleksi yang ada, misal untuk koleksi ID=1 viewnya “view_koleksi_1”, dst.