Julia กับ JIT

เรื่องนี้เคยโพสไปแล้วนะครับ เกี่ยว Julia และ Just-in-time compilation (JIT) จริง ๆ ก็มี compiler หลายภาษาที่ใช้เทคนิคนี้ครับ แต่ว่าชอบ Julia ตรงมันดึงเอา native code (แต่แสดงเป็นภาษา assembly ) ให้ดูได้สะดวกดี

ผมอ่าน Wikipedia มา ได้ความว่าคนที่ตีพิมพ์เรื่อง JIT ในคนแรก ๆ ก็คือเจ้าเดิมครับ John McCarthy ที่เป็นคนสร้างภาษา Lisp ในงานนี้ แต่ก็ต่างกับตอนเยอะเหมือนกัน เขาว่าแบบที่ใส่ memory แบบเดี๋ยวนี้ภาษาแรก ๆ คือ Smalltalk 80

ที่ไม่ได้แสดงให้ดูคือ Julia เป็นภาษา dynamic ดังนั้น f(x) นี่ใส่ f(1.8) ก็ได้ ได้ทั้ง float ทั้ง integer สมมุติว่าเราใส่ @code_native(f(1.8)) มันก็จะ gen native code ออกมาอีกแบบสำหรับ float แล้วสมมุติว่า f ถูกเรียกซ้ำ ๆ มันก็จะทำงานได้ไวเหมือนกับว่า compile เอาไว้แต่แรก

เทียบความเร็วโปรแกรมตัดคำที่เขียนด้วยภาษาต่าง ๆ

ผมลองตัดคำบนเอกสารภาษาไทยขนาด 20MB บนเครื่องบน Intel® Core™ i3-4030U CPU @ 1.90GHz × 4 ที่ลง Ubuntu 16.04 ใช้ JVM คือ openjdk version “1.8.0_111”

ใช้ data structure แบบเดียวกันหมด algorithm หลัก ๆ ก็คล้าย ๆ กันถ้าผมไม่พลาดอะไรไป

ผลออกมาโปรแกรม chamkho ที่เขียนด้วย Rust มันก็เร็วจริงตามคาด แต่ที่น่าแปลกใจคือ yaito ที่ใช้ Kotlin ผสม Clojure นิด ๆ เร็วกว่าใช้ Go เกือบเท่าตัว

update1: หลังจากที่คุณ @iporsut patch mapkha ไปตอนนี้เวลาดีขึ้นเยอะแล้วจาก 12 วินาที ลดมาเหลือ 9-10 วินาที แต่ว่าอันดับยังไม่เปลี่ยน

update2: หลังจากที่คุณ @iporsut patch ครั้งที่ 2, อันดับก็เปลี่ยนแล้วครับ mapkha ที่ใช้ Go ไวขึ้นแบบเหลือแค่ 5 วินาที ตามหลัง Rust มาติด ๆ

update3: ผมลองแก้ yaito ที่เขียนด้วย Kotlin เป็นหลักตามแบบคุณ @iporsut ที่นี้พบว่าอันดับเปลี่ยนอีกแล้ว yaito ตอนนี้เร็วที่สุด เหลือ 4.311 วินาทีครับ เร็วกว่า chamkho ที่เขียนด้วย Rust อีก แต่ยังไม่ได้ optimize chamkho

update4: หลังจากที่คุณ @iporsut tune การอ่านไฟล์ และผมเพิ่ม test case และแก้ bug ของ mapkha ไปครับ

update5: มีจุดน่าสังเกตอีกอย่างคือ JVM บางทีใช้ CPU มากกว่า 1 core ครับ ผมใช้คำสั่ง time แล้วเอาเวลา real มาเปรียบเทียบ มันจะขนาดมิติว่าถ้ารันหลาย ๆ งานไป

update6: ปรับ chamkho ที่เขียนด้วย Rust ให้ใช้วิธีคล้าย mapkha และ yaito พบว่า chamkho กลับมาเร็วที่สุดอีกครั้ง โดยใช้เวลา 3.284 วินาที

update7: คุณ @iporsut ปรับ mapkha ที่เขียนด้วย Go ให้ไม่ต้องสร้าง EdgeBuilder ใหม่บ่อย ๆ

update8: เพิ่ม Julia

update9: เพิ่ม JS

update10: เพิ่มภาษา Crystal

หากท่านใดต้องปรัปรุงโปรแกรมได้อีก ก็ขอเรียนเชิญ pull request มาได้เลยครับ มี project ทั้งบน gitlab และ github ชื่อเดียวกันครับ

ตารางทดสอบรอบที่ 1

โปรแกรม ภาษาโปรแกรม ครั้ง 1 (วินาที) ครั้ง 2 (วินาที) ครั้ง 3 (วินาที) เฉลี่ย (วินาที)
mapkha golang 1.7.4 12.733 12.651 12.441 12.608
mapkha (patch โดยคุณ @iporsut) golang 1.7.4 9.983 10.007 9.979 9.989
mapkha (patch โดยคุณ @iporsut ครั้งที่ 2) golang 1.7.4 5.062 4.938 5.014 5.005
mapkha (update 4) golang 1.7.4 5.415 5.405 5.416 5.412
mapkha (update 7) golang 1.7.4 5.431 5.449 5.385 5.422
yaito kotlin 1.0.6 มี clojure 1.8.0 นิดหน่อย 6.547 6.743 6.628 6.639
yaito ที่แก้ลอกเลียนคุณ @iporsut kotlin 1.0.6 มี clojure 1.8.0 นิดหน่อย 4.287 4.249 4.399 4.312
chamkho rust nightly 2017-01-08 4.826 4.829 4.85 4.835
chamkho update 6 rust nightly 2017-01-08 3.366 3.247 3.241 3.284
wordcut-clj clojure 1.8.0 มี kotlin 1.0.6 นิดหน่อย 63.502 67.561 67.303 66.122
wordcutpy python 3.5.2 50.624 50.803 50.869 50.765
wordcut.jl (update 8) Julia 0.5.0 38.316 38.112 38.237 38.221
prasae (update 9) node.js v6.5.0 49.349 49.084 49.901 49.445
kachet (update 10) Crystal 0.20.5 5.637 5.679 5.649 5.655

ป.ล. ขอบคุณท่าน @iporsut ที่ refactor Mapkha ให้สวยงามขึ้นครับ

ลอง Kotlin: กรณีโปรแกรมตัดคำ

ตัดคำเขียนด้วย Kotlin เกือบ ๆ หมดครับ มีส่วนอ่านไฟล์กับจัดการพวก command line argument ใช้ Clojure เพราะว่าไม่น่าจะมีผลอะไรกับความเร็วมาก

ชื่อโปรแกรมนึกไม่ออกจะเอาชื่อสถานที่ในระยองมาตั้งก็ยกมาแทบหมดแล้ว เห็นมี LexTo บน JVM ก็เลยตั้งชื่ออันนี้ว่า yaito แล้วกัน ในการพัฒนาก็ไม่ได้เกี่ยวอะไรกันเลย; yaito นี่พยายามเอา wordcut-py กับ wordcut-clj ยำกัน

ตอนใช้จริงๆ โหลด yaito-cliก็พอ ถ้า code สำคัญ ๆ อยู่ที่ yaito

หลังจากลองเขียนไปก็มีประเด็นมาเล่าเกี่ยวกับ Kotlin อยู่บ้าง

  • เขียนแบบข้างล่างเจ๊ง
    if (link?.isBetterThan(selectedLink)) {
    

    เพราะว่าเขียนข้างบนถ้า link เป็น null แล้ว ?.isBetterThan ก็จะ ตอบ null มาด้วยแล้ว แต่ if ต้องการ true/false

    ท่าแก้ท่านหึ่งคือเขียนแบบนี้ เพราะว่าผมมั่นใจว่า link ไม่เป็น null แน่ ๆ

    if (link?.isBetterThan(selectedLink)!!) {
    

    แต่ว่าใช้ link!!.isBetterThan แต่แรกไปเลยดีกว่า ?

  • เวลามี val path: Array ใน data class แล้วมันเตือน จริง ๆ คงเตือน array ทั้งหมด แต่ผมยังงง ๆ อยู่ว่าเดือนทำไม
  • ใน PatLinkBuilder ทีแรกเขียนแบบนี้
     var s = null
    

    แล้วพอพยายามเขียนให้ s = context.i เจ๊งเลย ต้องเปลี่ยนไปแบบข้างล่าง

     var s: Int? = null
    

    ก็เป็นอันใช้ได้

  • ลองเขียนแบบ
    open class PatLinkBuilder(val isAcceptable) : LinkBuilder
    

    แล้วเอาไปใช้แบบนี้

    class SpaceLinkBuilder: PatLinkBuilder( { it == ' '} )
    

    พบว่าเจ๊งต้องระบบ type ของ isAcceptable ถึงใช้ไ้ด

    open class PatLinkBuilder(val isAcceptable: (Char) -> Boolean) : LinkBuilder
    
  • ทีแรกเขียนแบบนี้
     val linkBuilder = arrayOf(DixLinkBuilder(dix)),
    

    แล้วเอา linkBuilder ไปใส่ให้ buildPath ก็เจ๊งเพราะมัน infer type ให้เป็น DixLinkBuilder array แต่เวลาใช้เจอ LinkBuilder array เวลาใช้งานก็ต้องไประบุ type ของ linkBuilder อยู่ดี

  • ชอบอันนี้
    return Link(s = context.leftBoundary,
                        linkType = LinkType.UNK,
                        wordCount = source.wordCount + 1,
                        unkCount = source.unkCount + 1)
    

    เวลาเจอ argument เยอะ ๆ มันจะพลาดได้นี่ใส่เป็น keyboard แทนได้ ต่อให้ check type มันก็พลาดง่าย ๆ อยู่ดี มี integer เพียบ

สรุปคือเรื่องจัดการ null ก็มึน ๆ นิดหน่อยแต่ก็แก้ได้ไม่ยากเท่าไหร่เพราะ IntelliJ IDEA มันเก่ง ถ้าใช้ Emacs เขียนท่าจะยาก; type inference มันใช้ได้ในกรณีง่าย ๆ กรณีไม่ง่ายก็ตามมาใส่เองอยู่ดี; ใส่ keyword ได้เจ๋งดี แต่ Python ก็มี; แต่รวม ๆ แล้วก็เขียนเสร็จไว ติดแต่ละประเด็นไม่นาน

ส่วะประสิทธิภาพออกมาประทับใจมาก จากเมื่อวานลอง wordcut-clj ซึ่งใช้ Clojure เยอะหน่อย ตัดคำบนไฟล์ 21 MB; จาก wordcut-clj ใช้เวลาประมาณ 1 นาที 8 วินาที ขนาดว่าใช้ prefixtree ที่ด้วย Kotlin แล้ว, ตัวใหม่ที่เขียนนี้ไวขึ้นเยอะตัดเสร็จใน 7.1 วินาที คือเร็วสูสี Rust เลย

เขียน Clojure ไปเรียก Kotlin อีกที

wordcut-clj เป็นโปรแกรมตัดตำที่เขียนด้วย Clojure ผมอยากจะเปลี่ยนจากใช้ sorted list ไปใช้ prefix-tree ที่เขียนด้วย Kotlin แทน

ขั้นแรกผมเอา prefixtree ไปใส่ใน Clojars ก่อนเลยจะได้เรียกมากใช้ง่าย ๆ เพิ่งทราบว่า push ด้วย Maven ก็ได้

แล้วก็เรียกมาใช้ผ่านทาง dep ปกติ

ใน Clojure ก็ import เข้ามาได้เหมือนเขียนด้วย Java เลย

(import net.veerkesto.PrefixTree)

แต่ว่าก็จะมีส่วนที่งง ๆ หน่อยคือ Pair ในเอกสารของ Kotlin บอกว่าอยู่ใน stdlib.kotlin แต่พอพยายามโหลดใช้ไม่ได้เป็น kotlin.Pair ได้เลย

(import kotlin.Pair)

ตอนเรียกใช้ data class ใน Kotlin ผมก็มั่ว ๆ เอาเวลาจะดึง childId ก็สั่งแบบนี้

(.getChildId child)

แต่พอพยายามจะ (.getIsFinal child) มันทำไม่ได้ พบว่าใช้

(.isFinal child)

แบบนี้ได้เลย ที่เหลือก็ตรง ๆ เหมือนเรียก Java ทั้งหมด ดู code เต็ม ๆ ได้ที่ gitlab ครับ

ตัดข้อความประมาณ 20MB ก็ใช้เวลาพอ ๆ กับ Python คือประมาณ 1 นาที 5-7 วินาที ซึ่งเร็วกว่า Python อยู่สัก 1-2 วินาที แน่นอนว่าช้ากว่าใช้ Rust มาก

ลองทำ maven repository ดู

ผมพยายามจะเอา .jar ขึ้น repository แบบ

lein deploy clojars

แต่ไม่รู้ว่า maven มันทำอย่างไร ก็เลยลองทำ repository ใช้เองดู ในเครื่องผมมี openjdk8 อยู่แล้ว

  1. ก็ไปเอา Apache Archiva มาลงได้เลย พอแตก zip ออกมาได้ก็ cd เข้า bin ไปรับ ./archiva console
  2. เข้า web port 8080
  3. สร้าง admin
  4. สร้าง folder ใน repositories/snapshots ตามชื่อ group-id, artifact-id, version เช่น veer66/prefixtree/1.0-SNAPSHOT
  5. เอาไฟล์ .jar เช่น prefixtree-1.0-SNAPSHOT.jar ไปใส่ใน veer66/prefixtree/1.0-SNAPSHOT ข้างบน
  6. เข้าไปเว็บสั่ง scan directory, สั่งทำ index ใน http://localhost:8080/#repositorylist

เสร็จแล้ว ผมก็ใช้ได้นะครับ แต่แน่นอนว่าผมมั่ว ๆ เอา ถ้าทำตามอาจจะมีรูรั่วหรือความเจ๊งอะไรเกิดขึ้นก็ได้ ซึ่งผมไม่สามารถรับผิดชอบได้

repo1

ไฟล์พวก .xml .md5 .sha1 นี่ Archiva มัน gen ให้เองครับ

ป.ล. คิดว่าตอนชาวบ้านเอาไปใช้วุ่นวายตาย ผมกะว่าพยายาม build Kotlin source code ใน lein แล้วเอาขึ้น clojars ดีกว่า 😛

ลอง Kotlin

พอดีมีดราม่าที่คุณ @iporsut ยกขึ้นมา ก็เลยไปอ่าน ๆ ดูก็รู้สึกว่าน่าสนใจดี

ก็เลยลอง port prefixtree จาก Rust มาลง Kotlin ดู code อยู่ที่ gitlab

เขียนง่ายถ้าเอาแบบตื้น ๆ ดู reference ก็เข้าใจเลย

เทียบกับ Scala ผมว่า Kotlin ง่ายกว่ามาก ๆ เพราะป่านนี้ผมยังไม่รู้ว่าจะเริ่มเขียน Scala อย่างไรเลย

เทียบกับ Java แล้วคิดว่าน่าจะยากง่าย ๆ พอ ๆ กัน ในคะแนน kotlin ตรงเขียน main แล้ว println ได้เลย แต่ตรงพวก data class รู้สึกว่ามันต้องเรียนรู้มากกว่า ๋Java

ข้อดีคือมันเขียนสั้นดี ไม่โดนหันเหความสนใจเพราะรายละเอียดที่มากเกินไป

Tomee 7.0.2, JSON date serialization, and workaround

When @Produces({ MediaType.APPLICATION_JSON }) and @XmlRootElement are put on a service and model classes respectively, Tomee serializes an entity to JSON automatically.

However, when java.util.Date is serialized, then the result was very strange, I expected this

"2012-01-02T11:12+07"

but what I actually got from Tomee 7.0.2 was

"25550102111200+0700"

So I tested with Tomee 1.7.4 instead of 7.0.2; and it works. Apache web site mentioned that Johnzon is used in Tomee 7.0.2, so I thought maybe the problem is there. I added a customized JohnzonConverter as follow:

In model:

@XmlRootElement
@Entity
public class Word {
...
    @JohnzonConverter(MyDateConverter.class)
    @Temporal(TemporalType.TIMESTAMP)
    private Date t1;
 ...
}

MyDateConverter.java:

import org.apache.johnzon.mapper.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import static java.util.Locale.ENGLISH;

public class MyDateConverter implements Converter<Date> {

    @Override
    public String toString(Date date) {
        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mmX", ENGLISH).format(date);
    }

    @Override
    public Date fromString(String s) {
        try {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mmX", ENGLISH).parse(s);
        } catch (ParseException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

Then even Tomee 7.0.2 works in the way I expected.

P.S. I added this to pom.xml too

<dependency>
  <groupId>org.apache.johnzon</groupId>
  <artifactId>johnzon-mapper</artifactId>
  <version>1.0.0</version>
  <scope>provided</scope>
</dependency>

 

chamkho – โปรแกรมตัดคำเขียนด้วย Rust ออกรุ่น 0.1.0 แล้ว

chamkho ก็คล้าย ๆ wordcutpy ล่ะครับ แต่เขียนด้วย Rust ใน version 0.1.0 ก็เปลี่ยนมาใช้ prefix tree บน hash map แทน sorted word list ผมลองเอาไฟล์ภาษาไทยประมาณ 20 MB มาลองตัดคำดู พบว่า version 0.0.13 ใช้เวลา 9.005 วินาที (เฉลี่ยมาจากการทดลอง 2 ครั้ง) พอแก้มาใช้ prefix tree ก็ใช้เวลาเหลือ 5.724 วินาที (เฉลี่ยจาก 2 ครั้งเหมือนกัน) สรุปว่าเร็วขึ้น 1.57 เท่า

ถ้าเทียบกับรุ่นที่ใช้ Python, Chamkho ก็ยังไวกว่าอยู่ประมาณ 11 เท่า

นอกจากเรื่อง data structure แล้วก็ยังมี option เพิ่มคือ  wordcut -s  เอาไว้ใส่ delimter ที่ต้องการได้ เช่นอยากจะเปลี่ยน delimiter จาก | เป็น whitespace ก็ใช้ wordcut -s ‘ ‘ ได้

ป.ล. chamkho ภาษาไทยคือ “ชำฆ้อ” ตั้งไว้เป็นที่ระลึกตำบลชำฆ้อ อำเภอเขาชะเมา จังหวัดระยอง

https://crates.io/crates/chamkho

wordcutpy เร็วขึ้นประมาณ 4 เท่า

wordcutpy เป็นโปรแกรมตัดคำที่เขียนด้วย Python3 ล้วน ๆ เร็วขึ้นกว่าเดิมประมาณ 4 เท่า จากที่ test โดยตัดคำจากไฟล์ใหญ่ประมาณ 20MB เพราะว่า commit นี้ เปลี่ยนจาก binary search มาใช้ prefix tree (trie?) ง่าย ๆ แบบยัดใส่ลงไปใน hash table ตารางเดียว

ดูที่ github ก็ได้ครับ