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/27
16進数文字列(String)⇔バイト配列(byte[]) プチ高速版
16進数文字列(String)⇔バイト配列(byte[])のプチ高速板を作成してみました。
2011/12/19
JavaのレガシーなCollectionクラスでもスレッドセーフではない?
JDK/JRE 1.x時代から存在するjava.util.Vectorなどのレガシーなクラスは、Thread-safe(スレッドセーフ)です。
これにも関わらず、思うような結果にならないケースがあります。
次は、検証用のサンプル(Java SE 5.0以降)です。
各数字がきれいに1個ずつ出力されないのです。
簡単な図解を作りました。
次のように、Vectorインスタンスにアクセスする範囲全体を排他する必要があります。
これにも関わらず、思うような結果にならないケースがあります。
次は、検証用のサンプル(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 : 999Vectorが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コンパイルが増えています。
このことから次のような推測が導かれます。
- (Java VM自体の性能改善よりもむしろ)java.nioパッケージを使うように改善したことによる性能差 or / and
- 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 SE | JITなし | JITあり | ||
---|---|---|---|---|
Hotspot ClientVM | Hotspot ServerVM | Hotspot ClientVM | Hotspot ServerVM | |
1.4.2 | 106967 | 114095 | 15716 | 12396 |
5.0 | 134080 | 140290 | 15898 | 11667 |
6 | 134502 | 129110 | 6987 | 4280 |
7 | 125772 | 112755 | 6013 | 3483 |
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をスローさせることができます。
逆に、すべての要素を処理する必要がないスレッドの場合は、InterruptedExceptionをキャッチして、次の処理に進めるか、あるいはスレッドを即時に終了することを選択することもできます。上記プログラムの場合、59行目のコメントを外せば、whileループを抜けて次の処理に進むことになります。
InterruptedExceptionのスローによって再開されたスレッドにおいて、どのように対応するかはユーザが自由に決めてもいいわけです。予期しないInterruptionなら無視し(NOP:NO Operationとする)、意図したInterruptionなら次の処理に進めることもできます。
一口で言ってしまうとケースバイケースだと考えています。ただ、対象のスレッドがどの処理を実行中であるのかを把握できていないとThread#interruptが効果的に働かないケースが多いのではないかと思うのです。また予期しない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がスローされる可能性もないわけではありません。
登録:
投稿 (Atom)