Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

31 January 2013

เทียบความเร็วระหว่าง binary กับ text files

ผมมักจะแนะนำให้นักศึกษาเก็บข้อมูลแบบ binary (หรือที่เก็บไฟล์ แล้วเราจะเรียกมันว่า binary file) เวลาที่ต้องการความเร็วในการอ่านเขียนข้อมูล และลดพื้นที่ในการเก็บข้อมูล แม้ว่าจะมีข้อเสียอยู่อย่างหนึ่ง คือ เราเปิดดูข้อมูลใน binary file ได้ไม่สะดวกนัก ไม่สามารถเปิด text editor มาแก้ binary file ได้ง่ายๆ แบบ text file แต่บางครั้งผมก็สงสัยว่า ถ้าเปรียบเทียบความเร็วในการเขียนอ่านข้อมูลระหว่าง binary file กับ text file แล้ว จะต่างกันมากน้อยแค่ไหน ผมเลยลองเขียนโปรแกรม Java ง่ายๆ ขึ้นมาทดลองดู ให้โปรแกรมสร้างไฟล์เก็บข้อมูล double จำนวน 10,000,000 ตัวลองไฟล์สองแบบ แล้วลองอ่านขึ้นมาดูว่าใช้เวลาต่างกันแค่ไหน

package org.cholwich.binvstxt;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class BinVsTxt {
  
  public void writeTextFile(String fname, int N) {
    try {
      Random r = new Random();
      r.setSeed(10241024);
      PrintWriter out = new PrintWriter(new FileWriter(fname));
      for(int i=0; i<N; i++) {
        out.println(r.nextDouble());
      }
      out.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  
  public void writeBinaryFile(String fname, int N) {
    try {
      Random r = new Random();
      r.setSeed(10241024);
      DataOutputStream out = new DataOutputStream(
                                new BufferedOutputStream(
                                    new FileOutputStream(fname)));
      for(int i=0; i<N; i++) {
        out.writeDouble(r.nextDouble());
      }
      out.close();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  
  public List<Double> readTextFile(String fname) {
    List<Double> l = new ArrayList<Double>(); 
    try {
      BufferedReader in = new BufferedReader(
                            new FileReader(fname));
      double d;
      String buf;
      while((buf = in.readLine()) != null) {
        d = Double.parseDouble(buf);
        l.add(d);
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (NumberFormatException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return l;
  }

  public List<Double> readBinaryFile(String fname) {
    List<Double> l = new ArrayList<Double>();
    DataInputStream in = null;
    try {
      in = new DataInputStream(
                new BufferedInputStream(
                    new FileInputStream(fname)));
      double d;
      while(true) {
        d = in.readDouble();
        l.add(d);
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (EOFException e) {
      try {
        in.close();
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return l;
  }
  
  public static void main(String[] args) {
    boolean read = true;
    boolean bin = true;
    final int N = 10000000;
    
    for(String s : args) {
      if (s.equals("write")) {
        read = false;
      }
      else if (s.equals("text")) {
        bin = false;
      }
    }
    BinVsTxt m = new BinVsTxt();
    long start = System.currentTimeMillis();
    if (read) {
      if (bin) {
        List<Double> l = m.readBinaryFile("out"+N+".dat");
        System.out.println(l.get(N-1));
      }
      else {
        List<Double> l = m.readTextFile("out"+N+".txt");
        System.out.println(l.get(N-1));
      }
    }
    else {
      if (bin) {
        m.writeBinaryFile("out"+N+".dat", N);
      }
      else {
        m.writeTextFile("out"+N+".txt", N);
      }
    }
    long stop = System.currentTimeMillis();
    long len = stop - start;
    System.out.println("Required time = " + len);
  }
}

เมื่อลองรันทั้งสี่แบบดูแล้ว ปรากฎว่าความเร็วที่ได้คือ

  • เขียน text file ใช้เวลา 6.109 วินาที
  • อ่าน text file ใช้เวลา 11.666 วินาที
  • เขียน binary file ใช้เวลา 0.583 วินาที
  • อ่าน binary file ใช้เวลา 3.698 วินาที
จึงสรุปได้ว่า การใช้ binary file เร็วกว่าการใช้ text file พอสมควร แต่อาจจะไม่จำเป็นเท่าไหร่ ถ้าไม่ได้อ่านเขียนข้อมูลจำนวนมหาศาล

01 September 2012

บวกเลข 128 บิต

เมื่อวานหลังจากสอนวิชา comp arch ซึ่งกำลังพูดถึงเรื่อง computer arithmetic ก็มีนักศึกษา (ที่ยังไม่ได้ถามว่าเจ้าตัวอยากจะให้ออกนามหรือเปล่า) สงสัยว่า ถ้าเราต้องการบวกเลขที่มีขนาดใหญ่กว่า 64 บิต ซึ่งเป็นขนาดที่คอมพิวเตอร์ปัจจุบันรองรับ จะทำย้งไง หลังจากอธิบายไปจนคิดว่าคนถามน่าจะเข้าใจแล้ว ก็เกิดอาการคันไม้คันมือเล็กน้อย เลยลองเขียนฟังก์ชันบวกเลขขนาด 128 บิต
ฟังก์ชันนี้ทำงานง่ายๆ คือ เก็บข้อมูลจำนวนเต็มขนาด 128 บิต โดยใช้ข้อมูลจำนวนเต็มขนาด 64 บิต 2 ตัวต่อกัน (เรียกเป็นครึ่งบน กับครึ่งล่างละกัน) เวลาจะบวกกัน ก็แค่ เอาครึ่งล่างบวกกัน เอาครึ่งบนบวกกัน แล้วถ้ามีทดจากครึ่งล่างก็ให้เอาไปบวกเพิ่มที่ครึ่งบนด้วย แค่นี้แหละ

#include <stdio.h>
#include <stdint.h>

typedef struct {
    int64_t hi;
    int64_t lo;
} int128_t;

int128_t add128(int128_t x, int128_t y) {
    int128_t z = {0,0};
    
    z.hi = x.hi + y.hi;
    z.lo = x.lo + y.lo;
    if (z.lo < x.lo) {
        z.hi++;
    }
    
    return z;
}

int main(int argc, const char * argv[])
{
    int128_t a = {0x0000000000000001, 0xffffffffffffffff};
    int128_t b = {0x0000000000000000, 0x0000000000000005};
    int128_t c;
    
    c = add128(a, b);
    
    printf("0x%016llx %016llx", c.hi, c.lo);
    
    return 0;
}

จากโปรแกรมนี้ จะได้ c = a+b โดยที่ทั้งหมดเป็นจำนวนเต็มขนาด 128 บิต ซึ่งเก็บในลักษณะ struct ประกอบด้วย hi กับ lo เป็นจำนวนเต็มขนาด 64 บิตทั้งคู่

จุดสำคัญของฟังก์ชันนี้ คือ การทดสอบว่าเกิดการทดเลขจากครึ่งล่างหรือไม่ โดยปกติ processor จะมี carry flag เอาไว้สำหรับเก็บค่าตัวทดหลังจากการบวกเลข แต่ภาษา C มีจุดอ่อนที่ไม่สามารถเรียกใช้ค่า carry flag ได้โดยตรง ถ้าจะทำแบบนั้นก็ต้องเขียน assembly ซึ่งดูไม่สะดวก ผมจึงใช้วิธีการตรวจสอบว่าเกิด overflow ขึ้นในการบวกเลขครึ่งล่างหรือไม่ ถ้าเกิด overflow ก็แสดงว่าจะต้องทดเลข หรือบวก 1 เข้าไปที่ผลบวกของครึ่งบน ตามลิงก์นี้ และต้องขอบคุณ @cutiening ที่ช่วยแสดงวิธี prove ว่า เมื่อ a+b แล้วเกิด overflow จะได้ว่าผลที่ได้ c < a และ c < b เสมอ ก็เลยได้ if statement ตามโปรแกรม เป็นอันเสร็จสิ้นการละเล่นแต่เพียงเท่านี้

ที่นี้บวกเลข 128 บิตได้แล้ว แต่ละแสดงผลลัพธ์ออกมาเป็นเลขฐานสิบได้ยังไง ก็ต้องเป็นคำถามต่อไป

16 August 2012

Virtual Method คืออะไร? (1)

Virtual method เป็นแนวคิดของ object-oriented programming ที่ไม่ค่อยเห็นกันเท่าไหร่ เพราะภาษาส่วนใหญ่ อย่างเช่น Java และ Python จะกำหนดให้ method ทุกอันเป็น virtual method ทั้งหมด คนที่เรียนใหม่ๆ จึงรับแนวคิดนี้ไปโดยไม่รู้ตัว ภาษาที่สามารถกำหนด metho d ได้ว่าเป็น virtual หรือไม่ ที่ผมพอรู้จักก็มี C++ และ C# พอดีวันก่อนผมโดนถามเกี่ยวกับเรื่องนี้ในภาษา C# ก็เลยขอเอามาเขียนเล่าไว้หน่อย เผื่อจะเป็นประโยชน์เวลาโดนถามอีก

Virtual method เกิดมาจากความคิดของ OOP ที่ต้องการขยายความสามารถของ class ที่สร้างไว้ก่อนแล้ว ด้วยวิธี inherit แล้ว override method เพื่อแก้ไขการทำงานบางส่วนของ class การใช้ virtual method ทำให้เราไม่ต้องตามไปแก้ไข method อื่นๆ ที่เรียกใช้ method ที่เราปรับปรุงทั้งหมด การระบุว่า method เป็น virtual method หมายความว่าให้เรียก method นั้นตาม object ที่สร้างขึ้นจริง ไม่ใช่เรียกตาม class ของตัวแปรที่สร้างขึ้น ลองดูตัวอย่างดีกว่า

ตัวอย่างแรกเป็น method แบบที่ไม่ใช่ virtual method

using System;

class A {
 public void print() {
  Console.WriteLine("This is A.");
 }
}

class B : A {
 public new void print() {
  Console.WriteLine("This is B.");
 }
}

class MyProgram {
 public static void Main() {
  A a1 = new A();
  a1.print();

  A a2 = new B();
  a2.print();

 }
}

โปรแกรมแรกนี้กำหนด Class A ซึ่งมี method ชื่อ print แล้วกำหนด Class B ให้เป็น subclass ของ A มี method ชื่อ print เช่นเดียวกัน (สังเกตว่าจะมี keyword ว่า new อยู่หน้า print ใน B อันนี้ C# เขาเรียกว่า method hiding คือการซ่อน method ของ superclass) เสร็จแล้วเรามี class MyProgram เอาไว้เป็น main program จะเห็นว่า ผมกำหนดตัวแปรสองตัว คือ a1 กับ a2 ตัวแปร a1 ชี้ไปที่ object ของ class A, ส่วน a2 ชื้ไปที่ object ของ class B (ปกติเราสามารถกำหนด object ของ subclass ให้กับตัวแปรของ superclass ได้อยู่แล้ว เพราะถือว่า subclass มีคุณสมบัติทุกอย่างของ superclass) เมื่อเรียกโปรแกรมนี้มาทำงาน จะได้

This is A.
This is A.

เหตุที่ผลลัพธ์เป็นอย่างนี้เพราะตัวแปร a1 และ a2 เป็นตัวแปรของ Class A เมื่อเรียก print ก็จะไปเรียก method แรกของ Class A มาทำงาน เราต้องคิดว่า object ของ Class B มีคุณสมบัติของ Class A รวมอยู่ด้วยแล้ว

ยังไม่ถึงเรื่อง virtual method เลย แต่วันนี้เอาไว้แค่นี้ก่อน วันหลังจะมาเขียนต่อ

14 August 2012

เขียนโปรแกรม C# บน Ubuntu

พอดีมีโอกาสได้ลองเขียนโปรแกรม C# บน Ubuntu โดยใช้ผ่าน Mono เลยขอจดกันลืมไว้หน่อยว่า จะต้องติดตั้ง package สองอัน คือ

$ sudo apt-get install mono-runtime mono-gmcs 

เมื่อติดตั้งเสร็จแล้ว ก็สามารถใช้งานได้ โดยลองเขียนโปรแกรม Hello, World ดู

using System;

class Hello {
 public static void Main() {
  Console.WriteLine("Hello, World");
 } 
}

ลอง compile ด้วยคำสั่ง gmcs จะได้ไฟล์ .exe สามารถทำงานได้

$ gmcs hello.cs

$ ./hello.exe
Hello, World

ที่มา: How to Compile and Run C# .NET application on Ubuntu