Skip to content

Instantly share code, notes, and snippets.

@tkawachi
Last active August 29, 2015 14:16
Show Gist options
  • Save tkawachi/48c97bcb6742fc860c22 to your computer and use it in GitHub Desktop.
Save tkawachi/48c97bcb6742fc860c22 to your computer and use it in GitHub Desktop.
Hello World! golf
yv66vgADAC0AEwwAEAASAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEADEhlbGxvIFdvcmxkIQcACwgAAwcADgEABENvZGUBAARtYWluCQAGAAEMAAwAEQEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuBwAHAQAQamF2YS9sYW5nL1N5c3RlbQoABAAKAQADb3V0AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAVTGphdmEvaW8vUHJpbnRTdHJlYW07ACEADQAEAAAAAAABAAkACAACAAEABwAAABUAAgABAAAACbIACRIFtgAPsQAAAAAAAA==

"Hello World!" を出す Java のクラスファイルってどれくらい小さくなるんだろう?

Scala

まずは慣れてる Scala で。

object S {
  def main(args: Array[String]) {
    println("Hello World!");
  }
}

scalac でコンパイルして

rw-r--r--  1 kawachi  staff  571 Mar  3 23:02 S$.class
-rw-r--r--  1 kawachi  staff  530 Mar  3 23:02 S.class

571 + 530 = 1101 bytes.

javap で中身を確認。

$ javap -c S.class 
Compiled from "A.scala"
public final class S {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #16                 // Field S$.MODULE$:LS$;
       3: aload_0
       4: invokevirtual #18                 // Method S$.main:([Ljava/lang/String;)V
       7: return
}
$ javap -c S$.class 
Compiled from "A.scala"
public final class S$ {
  public static final S$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class S$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public void main(java.lang.String[]);
    Code:
       0: getstatic     #19                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: ldc           #21                 // String Hello World!
       5: invokevirtual #25                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
       8: return
}

S.main から S$.main を呼んでる様子。無駄が多い。

Java

つづいて Java 最初から Java でやれという感がある。

class J {
  public static void main(String args[]) {
    System.out.println("Hello World!");
  }
}

javac すると

-rw-r--r--  1 kawachi  staff  408 Mar  3 23:07 J.class

408 bytes. Scala の半分以下だ。

javap してみる。

$ javap -c J.class 
Compiled from "A.java"
class J {
  J();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Hello World!
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

Hello World を印字するだけなら J(); の部分は要らないのではないか?

Jasmin

.class public M
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
  .limit stack 2
  getstatic java/lang/System/out Ljava/io/PrintStream;
  ldc "Hello World!"
  invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
  return
.end method

とりあえず main だけあればいいよね。

コンパイルしてサイズは

-rw-r--r--  1 kawachi  staff  303 Mar  3 23:13 M.class

303 bytes.

javap して

$ javap -c M.class 
Compiled from "M.j"
public class M {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #7                  // String Hello World!
       5: invokevirtual #19                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

手作業

Jasmine で生成されたクラスファイルを見てみる。

$ hexdump -C M.class 
00000000  ca fe ba be 00 03 00 2d  00 18 0c 00 14 00 17 01  |.......-........|
00000010  00 16 28 5b 4c 6a 61 76  61 2f 6c 61 6e 67 2f 53  |..([Ljava/lang/S|
00000020  74 72 69 6e 67 3b 29 56  01 00 10 6a 61 76 61 2f  |tring;)V...java/|
00000030  6c 61 6e 67 2f 4f 62 6a  65 63 74 07 00 03 01 00  |lang/Object.....|
00000040  0c 48 65 6c 6c 6f 20 57  6f 72 6c 64 21 07 00 0f  |.Hello World!...|
00000050  08 00 05 07 00 12 01 00  04 43 6f 64 65 01 00 04  |.........Code...|
00000060  6d 61 69 6e 09 00 08 00  01 01 00 01 4d 01 00 0a  |main........M...|
00000070  53 6f 75 72 63 65 46 69  6c 65 0c 00 10 00 15 01  |SourceFile......|
00000080  00 13 6a 61 76 61 2f 69  6f 2f 50 72 69 6e 74 53  |..java/io/PrintS|
00000090  74 72 65 61 6d 01 00 07  70 72 69 6e 74 6c 6e 07  |tream...println.|
000000a0  00 0c 01 00 10 6a 61 76  61 2f 6c 61 6e 67 2f 53  |.....java/lang/S|
000000b0  79 73 74 65 6d 0a 00 06  00 0e 01 00 03 6f 75 74  |ystem........out|
000000c0  01 00 15 28 4c 6a 61 76  61 2f 6c 61 6e 67 2f 53  |...(Ljava/lang/S|
000000d0  74 72 69 6e 67 3b 29 56  01 00 03 4d 2e 6a 01 00  |tring;)V...M.j..|
000000e0  15 4c 6a 61 76 61 2f 69  6f 2f 50 72 69 6e 74 53  |.Ljava/io/PrintS|
000000f0  74 72 65 61 6d 3b 00 21  00 11 00 04 00 00 00 00  |tream;.!........|
00000100  00 01 00 09 00 0a 00 02  00 01 00 09 00 00 00 15  |................|
00000110  00 02 00 01 00 00 00 09  b2 00 0b 12 07 b6 00 13  |................|
00000120  b1 00 00 00 00 00 01 00  0d 00 00 00 02 00 16     |...............|
0000012f
  • 末尾の 00 0d 00 00 00 02 00 16 は SourceFile 属性を表しているので削れそう。
    • constant pool の "SourceFile" (0x0d), "M.j" (0x16) も削れる。
  • クラス名を M としていたが、 Code という名前にすれば Code 属性で利用している constant pool が使いまわせる。

目視で 1 byte ずつ丁寧に書き換えたのがこれ。 constant pool の index が変わると変更箇所がいろいろ出てきてめんどい。

$ hexdump -C Code.class 
00000000  ca fe ba be 00 03 00 2d  00 15 0c 00 12 00 14 01  |.......-........|
00000010  00 16 28 5b 4c 6a 61 76  61 2f 6c 61 6e 67 2f 53  |..([Ljava/lang/S|
00000020  74 72 69 6e 67 3b 29 56  01 00 10 6a 61 76 61 2f  |tring;)V...java/|
00000030  6c 61 6e 67 2f 4f 62 6a  65 63 74 07 00 03 01 00  |lang/Object.....|
00000040  0c 48 65 6c 6c 6f 20 57  6f 72 6c 64 21 07 00 0d  |.Hello World!...|
00000050  08 00 05 07 00 10 01 00  04 43 6f 64 65 01 00 04  |.........Code...|
00000060  6d 61 69 6e 09 00 08 00  01 0c 00 0e 00 13 01 00  |main............|
00000070  13 6a 61 76 61 2f 69 6f  2f 50 72 69 6e 74 53 74  |.java/io/PrintSt|
00000080  72 65 61 6d 01 00 07 70  72 69 6e 74 6c 6e 07 00  |ream...println..|
00000090  09 01 00 10 6a 61 76 61  2f 6c 61 6e 67 2f 53 79  |....java/lang/Sy|
000000a0  73 74 65 6d 0a 00 06 00  0c 01 00 03 6f 75 74 01  |stem........out.|
000000b0  00 15 28 4c 6a 61 76 61  2f 6c 61 6e 67 2f 53 74  |..(Ljava/lang/St|
000000c0  72 69 6e 67 3b 29 56 01  00 15 4c 6a 61 76 61 2f  |ring;)V...Ljava/|
000000d0  69 6f 2f 50 72 69 6e 74  53 74 72 65 61 6d 3b 00  |io/PrintStream;.|
000000e0  21 00 0f 00 04 00 00 00  00 00 01 00 09 00 0a 00  |!...............|
000000f0  02 00 01 00 09 00 00 00  15 00 02 00 01 00 00 00  |................|
00000100  09 b2 00 0b 12 07 b6 00  11 b1 00 00 00 00 00 00  |................|
00000110

272 bytes まで減った。 実行もできる。

$ java Code
Hello World!
$ javap -c Code.class 
public class Code {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #7                  // String Hello World!
       5: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

親クラスで参照している java.lang.Object が無駄(文字列的に)という示唆をいただいた。 https://twitter.com/kazzna/status/573409436124905472

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1 の super_class をみると

For a class, the value of the super_class item either must be zero or must be a valid index into the constant_pool table.

とかいており、 0x00 にしておけば java.lang.Object を constant pool から削れて短くなるのでは!?と思ったが

Exception in thread "main" java.lang.ClassFormatError: Invalid superclass index 
0 in class file Code

というエラーに。。。 よく読んだら

If the value of the super_class item is zero, then this class file must represent the class Object, ...

と書いてあった。

というわけで、既に constant pool に存在している PrintStream を super_class に変更して、 java/lang/Object の Utf8_info と Class_info を削除。 バイナリエディタで丁寧に削っていく。

$ hexdump -C Code.class
00000000  ca fe ba be 00 03 00 2d  00 13 0c 00 10 00 12 01  |.......-........|
00000010  00 16 28 5b 4c 6a 61 76  61 2f 6c 61 6e 67 2f 53  |..([Ljava/lang/S|
00000020  74 72 69 6e 67 3b 29 56  01 00 0c 48 65 6c 6c 6f  |tring;)V...Hello|
00000030  20 57 6f 72 6c 64 21 07  00 0b 08 00 03 07 00 0e  | World!.........|
00000040  01 00 04 43 6f 64 65 01  00 04 6d 61 69 6e 09 00  |...Code...main..|
00000050  06 00 01 0c 00 0c 00 11  01 00 13 6a 61 76 61 2f  |...........java/|
00000060  69 6f 2f 50 72 69 6e 74  53 74 72 65 61 6d 01 00  |io/PrintStream..|
00000070  07 70 72 69 6e 74 6c 6e  07 00 07 01 00 10 6a 61  |.println......ja|
00000080  76 61 2f 6c 61 6e 67 2f  53 79 73 74 65 6d 0a 00  |va/lang/System..|
00000090  04 00 0a 01 00 03 6f 75  74 01 00 15 28 4c 6a 61  |......out...(Lja|
000000a0  76 61 2f 6c 61 6e 67 2f  53 74 72 69 6e 67 3b 29  |va/lang/String;)|
000000b0  56 01 00 15 4c 6a 61 76  61 2f 69 6f 2f 50 72 69  |V...Ljava/io/Pri|
000000c0  6e 74 53 74 72 65 61 6d  3b 00 21 00 0d 00 04 00  |ntStream;.!.....|
000000d0  00 00 00 00 01 00 09 00  08 00 02 00 01 00 07 00  |................|
000000e0  00 00 15 00 02 00 01 00  00 00 09 b2 00 09 12 05  |................|
000000f0  b6 00 0f b1 00 00 00 00  00 00                    |..........|
000000fa

$ java Code
Hello World!

$ javap -c Code
public class Code extends java.io.PrintStream {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #5                  // String Hello World!
       5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

$ ls -l Code.class
-rw-r--r--  1 kawachi  staff  250 Mar  5 19:02 Code.class

250 bytes まできた!

@tkawachi
Copy link
Author

tkawachi commented Mar 3, 2015

@xuwei-k
Copy link

xuwei-k commented Mar 5, 2015

http://codegolf.stackexchange.com stackoverflowの姉妹サイトにcodegolf用のやつあるらしいので、そこに投稿してみるとか

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment