2011/12/27

16進数文字列(String)⇔バイト配列(byte[]) プチ高速版

16進数文字列(String)⇔バイト配列(byte[])のプチ高速板を作成してみました。
public class HexUtil2 {
    private static final char[] digits = {
  '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' ,
  'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
  'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z'
 };

 public static String toString(int i, int radix, int length) {
  final char[] buf = new char[length];
  final int mask = radix - 1;
  int charPos = length;
  do {
   buf[--charPos] = digits[i & mask];
   i /= radix;
  } while (i != 0);
  while (charPos > 0) {
   buf[--charPos] = '0';
  }

  return new String(buf, charPos, (length - charPos));
 }

 public static String toString(byte[] bytes, int radix, int length) {
  final StringBuffer sb = new StringBuffer(bytes.length * length);
  for (int i=0; i<bytes.length; i++) {
   sb.append(toString(bytes[i] & 0xff, radix, length)); //自然数にしたバイト値を、16進数文字列に変換
  }
  return sb.toString();
 }

 public static byte[] toByteArray(String s, int radix, int length) {
  final char[] buf = s.toCharArray();
  final byte[] bytes = new byte[buf.length / length];
  for (int i=0; i<buf.length/length; i++) {
   bytes[i] = (byte)Integer.parseInt(new String(buf, i*length, length), radix);
  }
  return bytes;
 }

2011/12/19

JavaのレガシーなCollectionクラスでもスレッドセーフではない?

JDK/JRE 1.x時代から存在するjava.util.Vectorなどのレガシーなクラスは、Thread-safe(スレッドセーフ)です。
これにも関わらず、思うような結果にならないケースがあります。
次は、検証用のサンプル(Java SE 5.0以降)です。
package a;

import java.util.Arrays;
import java.util.List;
import java.util.Vector;

class A implements Runnable {
 //VectorはThread-safeである
 private List<String> v = new Vector<String>();
 
 public void run() {
  for(int i=0; i<1000; i++){
   //3桁の数字文字列を生成
   final String s = createNumberString(i);
   //文字列sがVectorに含まれていなければ、追加する
   if(v.contains(s) == false){
    v.add(s);
   }
  }
 }

 //3桁の文字列を生成する(例:1→"001")
 private static String createNumberString(int i){
  final StringBuilder sb = new StringBuilder(String.valueOf(i));
  while(sb.length() < 3){
   sb.insert(0, "0");
  }
  return sb.toString();
 }

 public static void main(String[] args) throws InterruptedException {
  //多重度3でA.run()の処理を実行する
  final A a = new A();
  final Thread[] tt = new Thread[3];
  for(int i=0; i<tt.length; i++){
   tt[i] = new Thread(a);
   tt[i].start();
  }
  for(Thread t : tt){
   t.join();
  }

  //処理結果を出力
  final List<String> v = a.v;
  final String[] ss = v.toArray(new String[v.size()]);
  Arrays.sort(ss);
  for(String s : ss){
   System.out.println(s);
  }
 }
}
上記プログラムでは、実行するたびに結果が変わりますが、次のように同じ数字が2か3個出力されるでしょう。
各数字がきれいに1個ずつ出力されないのです。
000
001
002
 :
169
169
170
170
171
171
172
172
173
173
 :
999
VectorがThread-safeなのは、Vectorインスタンス内部に限ります。つまりVectorインスタンスの内部ではデータの整合性は保たれますが、Vectorを使う側(上記プログラムではAクラス)では必ずしもデータを正しく操作できることを保証するものではありません。
簡単な図解を作りました。


To use the legacy Collection is not necessarily thread-safe.

次のように、Vectorインスタンスにアクセスする範囲全体を排他する必要があります。
 public void run() {
  final List<String> v = this.v;
  for(int i=0; i<1000; i++){
   //3桁の数字文字列を生成
   final String s = createNumberString(i);
   synchronized(v){
    //文字列sがVectorに含まれていなければ、追加する
    if(v.contains(s) == false){
     v.add(s);
    }
   }
  }
 }
なお今回のようなケースではAクラスでのsynchronizedとVector内部でのsynchronizedとロックが二重になりますから、スレッドセーフではないjava.util.ArrayListを使う方が、速度性能が良くなりますね。

2011/12/06

文字列操作の速度を測ってみた(やっつけ3)

文字列操作の速度を測ってみた(やっつけ2)について、もう少し突っ込んで調べました。

今度は、Java VMオプションを「-server -XX:+PrintCompilation」にして、Java 1.4.2、5.0、6、7のそれぞれで実行してみました。すると、Java 5.0と6との間で出力内容に大きな変化が見られました。

Java SE 5.0の場合

  1       java.io.Win32FileSystem::normalize (143 bytes)
  2  !    java.net.URLEncoder::encode (378 bytes)
  3       java.lang.Character::isLetter (158 bytes)
  4       sun.nio.cs.UTF_8$Encoder::encodeArrayLoop (490 bytes)
  5       java.nio.Buffer:: (68 bytes)
  6  !    java.nio.charset.CharsetEncoder::encode (285 bytes)
  7*      java.lang.System::arraycopy (0 bytes)
  8       java.nio.Buffer::position (43 bytes)
  9       java.nio.Buffer::limit (62 bytes)
 10  !    java.lang.StringCoding::encode (127 bytes)
 11       sun.nio.cs.UTF_8$Encoder::encodeLoop (28 bytes)
 12  !    java.io.CharArrayWriter::toCharArray (37 bytes)
 13       java.lang.AbstractStringBuilder::expandCapacity (51 bytes)
 14 s     java.lang.StringBuffer::toString (17 bytes)
 15       java.lang.AbstractStringBuilder:: (12 bytes)
 16       java.nio.charset.Charset::forName (20 bytes)
 17       java.io.CharArrayWriter:: (7 bytes)
  1%      b.A::measure @ 14 (47 bytes)
 18       b.A::measure (47 bytes)       //この行以降が2回目のmeasureメソッド呼出し後
  2%      b.A::measure @ 14 (47 bytes)

Java SE 6の場合

    195   1       java.lang.String::charAt (33 bytes)
    196   2       java.lang.AbstractStringBuilder::append (40 bytes)
    229   3 s     java.lang.StringBuffer::append (8 bytes)
    231   4       java.lang.CharacterDataLatin1::getProperties (11 bytes)
    232   5  !    java.net.URLEncoder::encode (375 bytes)
    234   6       java.lang.Character::forDigit (42 bytes)
    234   7       java.lang.Character::isLetter (5 bytes)
    235   8       java.lang.Character::isLetter (158 bytes)
    236   9       java.lang.CharacterDataLatin1::isLetter (20 bytes)
    237  10       java.lang.CharacterDataLatin1::getType (10 bytes)
    237  11       java.util.BitSet::wordIndex (5 bytes)
    237  12       java.util.BitSet::checkInvariants (111 bytes)
    238  13       java.util.BitSet::get (69 bytes)
    245  14       java.lang.Object:: (1 bytes)
    250  15       sun.nio.cs.UTF_8$Encoder::encodeArrayLoop (490 bytes)
    261  16       java.lang.Math::min (11 bytes)
    267  17       java.nio.Buffer::position (43 bytes)
    267  18       java.nio.charset.CoderResult::isUnderflow (13 bytes)
---   n   java.lang.System::arraycopy (static)
    287  19       java.nio.Buffer::limit (62 bytes)
    287  20       java.nio.Buffer:: (68 bytes)
    306  21       java.lang.String::equals (88 bytes)
    326  22       java.nio.Buffer::hasRemaining (17 bytes)
    326  23       java.nio.ByteBuffer:: (45 bytes)
    326  24  !    java.nio.Bits::byteOrder (119 bytes)
    326  25       java.nio.CharBuffer::hasArray (20 bytes)
    327  26       java.nio.ByteBuffer::hasArray (20 bytes)
    327  27       java.nio.charset.CoderResult::isOverflow (14 bytes)
    327  28       java.nio.CharBuffer:: (22 bytes)
    327  29  !    java.nio.ByteBuffer::wrap (20 bytes)
    328  30       java.nio.HeapByteBuffer:: (14 bytes)
    329  31  !    java.nio.CharBuffer::wrap (20 bytes)
    329  32       java.nio.HeapCharBuffer:: (14 bytes)
    330  33       java.lang.StringCoding::access$000 (6 bytes)
    330  34       java.lang.StringCoding::scale (7 bytes)
    331  35       java.nio.charset.Charset::atBugLevel (53 bytes)
    331  36       java.nio.ByteBuffer::wrap (8 bytes)
    332  37       java.nio.charset.CharsetEncoder:: (16 bytes)
    332  38       java.nio.charset.CharsetEncoder:: (113 bytes)
    334  39       java.nio.charset.CharsetEncoder::replaceWith (81 bytes)
    334  40       java.nio.charset.CharsetEncoder::onMalformedInput (26 bytes)
    335  41       java.nio.charset.CharsetEncoder::onUnmappableCharacter (26 bytes)
    335  42       java.util.Arrays::copyOf (19 bytes)
    335  43       java.lang.StringCoding$StringEncoder:: (7 bytes)
    336  44       java.lang.StringCoding$StringEncoder:: (35 bytes)
    338  45  !    java.lang.StringCoding$StringEncoder::encode (130 bytes)
    339  46       java.nio.charset.CharsetEncoder::maxBytesPerChar (5 bytes)
    339  47       java.nio.charset.CharsetEncoder::reset (11 bytes)
    339  48  !    java.nio.charset.CharsetEncoder::encode (285 bytes)
    345  49       java.nio.charset.CharsetEncoder::flush (49 bytes)
    345  50       java.nio.charset.CharsetEncoder::implFlush (4 bytes)
    345  51       java.lang.StringCoding::access$300 (7 bytes)
    347  52       java.lang.StringCoding::safeTrim (30 bytes)
    349  53       sun.nio.cs.UTF_8::newEncoder (10 bytes)
    350  54       sun.nio.cs.UTF_8$Encoder:: (6 bytes)
    351  55       sun.nio.cs.UTF_8$Encoder:: (10 bytes)
    352  56       sun.nio.cs.UTF_8$Encoder::isLegalReplacement (26 bytes)
    352  57       sun.nio.cs.UTF_8$Encoder::encodeLoop (28 bytes)
    462  58       java.util.Arrays::copyOfRange (63 bytes)
    463  59       java.nio.charset.Charset::lookup (44 bytes)
    465  60       java.nio.charset.Charset::forName (20 bytes)
    520   1%      b.A::measure @ 14 (47 bytes)
   7009   1%     made not entrant  b.A::measure @ -2 (47 bytes)
   7010  48  !   made not entrant  java.nio.charset.CharsetEncoder::encode (285 bytes)
   7010  61       b.A::measure (47 bytes)       //この行以降が2回目のmeasureメソッド呼出し後
   7014   2%      b.A::measure @ 14 (47 bytes)

java.nioパッケージのAPIに対するJITコンパイルが増えています。
このことから次のような推測が導かれます。

  1. (Java VM自体の性能改善よりもむしろ)java.nioパッケージを使うように改善したことによる性能差 or / and
  2. java.nioパッケージ自体の改善による性能差
これ以上の調査は辛いので、今回はここまで。

文字列操作の速度を測ってみた(やっつけ2)

Java1.4.2~Java7.0で文字列操作の速度を測ってみた(やっつけ)を見て気になったので、こちらでも少し測定してみました。

元の記事のものに少しだけ手を加えたプログラムを用意しました。

package b;

import java.net.URLDecoder;

public class A {
 private static final String URL = "http://www.google.co.jp/?q=URLEncoder%E3%81%AF%E3%81%A9%E3%82%8C%E3%81%8F%E3%82%89%E3%81%84%E9%81%85%E3%81%84%E3%81%AE%E3%81%8B%E3%80%81commons-codec%E3%81%A8%E6%AF%94%E8%BC%83%E3%81%97%E3%81%A6%E3%81%BF%E3%81%BE%E3%81%99";
 private static final int TIMES = 1000000;

 public static void main(String[] args) throws Throwable {
  measure();
  measure();
 }

 public static void measure() throws Throwable {
  final String input = URLDecoder.decode(URL, "UTF-8");

  final long startTime = System.currentTimeMillis();
  for (int i=0; i<TIMES; i++) {
   java.net.URLEncoder.encode(input, "UTF8");

  }
  final long endTime = System.currentTimeMillis();

  System.out.println(endTime - startTime);
 }
}

次のようなバッチファイルで測定しました。

SET MYOPTS=-Xint -client
D:\jdk4\bin\java -cp bin %MYOPTS% b.A
D:\jdk5\bin\java -cp bin %MYOPTS% b.A
D:\jdk6\bin\java -cp bin %MYOPTS% b.A
D:\jdk7\bin\java -cp bin %MYOPTS% b.A

SET MYOPTS=-Xint -server
D:\jdk4\bin\java -cp bin %MYOPTS% b.A
D:\jdk5\bin\java -cp bin %MYOPTS% b.A
D:\jdk6\bin\java -cp bin %MYOPTS% b.A
D:\jdk7\bin\java -cp bin %MYOPTS% b.A

SET MYOPTS=-client
D:\jdk4\bin\java -cp bin %MYOPTS% b.A
D:\jdk5\bin\java -cp bin %MYOPTS% b.A
D:\jdk6\bin\java -cp bin %MYOPTS% b.A
D:\jdk7\bin\java -cp bin %MYOPTS% b.A

SET MYOPTS=-server
D:\jdk4\bin\java -cp bin %MYOPTS% b.A
D:\jdk5\bin\java -cp bin %MYOPTS% b.A
D:\jdk6\bin\java -cp bin %MYOPTS% b.A
D:\jdk7\bin\java -cp bin %MYOPTS% b.A

測定結果を次に示します(2回目の測定値のみ)。

Java SEJITなしJITあり
Hotspot ClientVMHotspot ServerVMHotspot ClientVMHotspot ServerVM
1.4.21069671140951571612396
5.0 1340801402901589811667
6 13450212911069874280
7 12577211275560133483

Java SE 5.0とJava SE 6のJIT (Just In Time)コンパイラの性能差が目立ちます。
また反復処理が多い場合は、Java Hotspot ServerVMの方が良さそうですね。

2011/12/04

InterruptedExceptionは何のためにある?

InterruptedExceptionがスローされるメソッドは結構ありますが、
停止状態(ブロック状態)にあるスレッドを再開するためにInterruptedExceptionをスローするというのが基本的な考え方であって、スレッドをinterruptするのではありません
言い換えると、InterruptedExceptionは、停止状態(ブロック状態)にある処理を一度キャンセルしてスレッドの処理を再開させるためにあっても、必ずしもブロックの原因となった処理を繰り返し実行してはいけないというわけではないと考えています。

次のようなプログラムを考えてみます。
Queueから要素を取り出すスレッド(11行目で非デーモンに設定)が起動されます。このスレッドは非デーモンなので、mainスレッドが終了してもアプリケーションは即座に終了しないようになっています。
一方、mainスレッドではQueueに10個の要素を挿入し、11個目の要素の挿入で処理を終えるように指示しています。
なお実験的に20行目のコメントを外して、InterruptedExceptionをスローさせることができます。
package queue;

import java.util.concurrent.LinkedBlockingQueue;

public final class A {
 private static final LinkedBlockingQueue<Item> queue = new LinkedBlockingQueue<Item>();

 public static void main(String[] args) {
  //QueueからItemを取りだすスレッドを起動
  final Thread thread = new Thread(new Service());
  thread.setDaemon(false);
  thread.start();

  //QueueにItemを入れる
  for(int i=0; i<10; i++){
   queue.add(new Item(false, "Msg"+String.valueOf(i)));
  }

  //BlockingQueue#takeでInterruptedExceptionをスローさせる
//  thread.interrupt();

  //最後のItemを入れる
  queue.add(new Item(true, null));
 }

 //Item
 static class Item {
  private final boolean end; //trueなら最後のItemであることを示す
  private final String msg;

  Item(boolean end, String msg){
   this.end = end;
   this.msg = msg;
  }

  boolean isEnd(){
   return this.end;
  }

  String getMsg(){
   return this.msg;
  }
 }

 //QueueからItemを取りだすスレッド
 static class Service implements Runnable {
  public void run() {
   boolean roop = true;
   while(roop){
    try {
     final Item item = queue.take(); //throws InterruptedException
     if(item.isEnd()==true){
      roop = false;    //ループを抜ける
     }else{
      System.out.println(item.getMsg());
     }
    } catch (InterruptedException e) {
     System.out.println("InterruptedException was thrown.");
//     roop = false;
    }
   }
  }
 }
}
このスレッドでQueueにある要素をすべて処理しなければならないと考えるならば、InterruptedExceptionを無視して処理を継続します。たとえば、LoggingのようにすべてのLogを吐き出したいときにはInterruptedExceptionを無視することを選択するでしょう。
逆に、すべての要素を処理する必要がないスレッドの場合は、InterruptedExceptionをキャッチして、次の処理に進めるか、あるいはスレッドを即時に終了することを選択することもできます。上記プログラムの場合、59行目のコメントを外せば、whileループを抜けて次の処理に進むことになります。
InterruptedExceptionのスローによって再開されたスレッドにおいて、どのように対応するかはユーザが自由に決めてもいいわけです。予期しないInterruptionなら無視し(NOP:NO Operationとする)、意図したInterruptionなら次の処理に進めることもできます。
一口で言ってしまうとケースバイケースだと考えています。ただ、対象のスレッドがどの処理を実行中であるのかを把握できていないとThread#interruptが効果的に働かないケースが多いのではないかと思うのです。また予期しないInterruptedExceptionがスローされる可能性もないわけではありません。