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がスローされる可能性もないわけではありません。

2011/09/23

"Strings in switch" in Java 7 (JSR 334)

Java 7で導入されたJSR 334には、「Strings in switch」がありますが、switch構文でString型も扱えるようになっています。それがどのようにコンパイルされるのかを調べてみました。



package a;
public class E {
  public static int a(String s){
    switch(s){
    case "a":
      return 1;
    case "b":
      return 2;
    default:
      return 3;
    }
  }
}



上記ソースをjavacでコンパイルして、javap -verboseしてみました。


Classfile /E:/tmp/java/java7/bin/a/E.class
  Last modified 2011/09/23; size 491 bytes
  MD5 checksum 525f1ebe51742f599543582cb206c23c
  Compiled from "E.java"
public class a.E
  SourceFile: "E.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER

Constant pool:
   #1 = Methodref          #7.#18         //  java/lang/Object."<init>":()V
   #2 = Methodref          #19.#20        //  java/lang/String.hashCode:()I
   #3 = String             #12            //  a
   #4 = Methodref          #19.#21        //  java/lang/String.equals:(Ljava/lang/Object;)Z
   #5 = String             #22            //  b
   #6 = Class              #23            //  a/E
   #7 = Class              #24            //  java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               a
  #13 = Utf8               (Ljava/lang/String;)I
  #14 = Utf8               StackMapTable
  #15 = Class              #25            //  java/lang/String
  #16 = Utf8               SourceFile
  #17 = Utf8               E.java
  #18 = NameAndType        #8:#9          //  "<init>":()V
  #19 = Class              #25            //  java/lang/String
  #20 = NameAndType        #26:#27        //  hashCode:()I
  #21 = NameAndType        #28:#29        //  equals:(Ljava/lang/Object;)Z
  #22 = Utf8               b
  #23 = Utf8               a/E
  #24 = Utf8               java/lang/Object
  #25 = Utf8               java/lang/String
  #26 = Utf8               hashCode
  #27 = Utf8               ()I
  #28 = Utf8               equals
  #29 = Utf8               (Ljava/lang/Object;)Z
{
  public a.E();
    flags: ACC_PUBLIC

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return        
      LineNumberTable:
        line 3: 0

  public static int a(java.lang.String);
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=2, locals=3, args_size=1
         0: aload_0       
         1: astore_1      
         2: iconst_m1     
         3: istore_2      
         4: aload_1       
         5: invokevirtual #2                  // Method java/lang/String.hashCode:()I
         8: lookupswitch  { // 2

                      97: 36

                      98: 50
                 default: 61
            }
        36: aload_1       
        37: ldc           #3                  // String a
        39: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
        42: ifeq          61
        45: iconst_0      
        46: istore_2      
        47: goto          61
        50: aload_1       
        51: ldc           #5                  // String b
        53: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
        56: ifeq          61
        59: iconst_1      
        60: istore_2      
        61: iload_2       
        62: lookupswitch  { // 2

                       0: 88

                       1: 90
                 default: 92
            }
        88: iconst_1      
        89: ireturn       
        90: iconst_2      
        91: ireturn       
        92: iconst_3      
        93: ireturn       
      LineNumberTable:
        line 5: 0
        line 7: 88
        line 9: 90
        line 11: 92
      StackMapTable: number_of_entries = 6
           frame_type = 253 /* append */
             offset_delta = 36
        locals = [ class java/lang/String, int ]
           frame_type = 13 /* same */
           frame_type = 10 /* same */
           frame_type = 26 /* same */
           frame_type = 1 /* same */
           frame_type = 1 /* same */

}



案の定ハッシュ値を使っていて、次のような流れで処理していることが判ります。

  1. ローカル変数2を-1で初期化し(60~61行目)、
  2. 引数のString型のハッシュ値を取得し(63行目)、
  3. 1つ目のlookupswitchでハッシュ値が同値なら、さらにString.equals()で評価し(64~84行目)、
  4. 評価結果をローカル変数2に数値を代入して(0または1)(75、76、82、83行目)、
  5. 2つ目のlookupswitchで、ローカル変数2の値に応じた処理を行う(84~97行目)

2つのlookupswitchを使って条件分岐しているのは意外でした。

2011/09/19

娘の減圧症について

■はじめに
我が家では夫婦ともダイバーで経験本数は共に200本以上で、そこそこの経験と知識を有しています。
そして、娘(10歳)の希望で、PADIのジュニアオープンウォーターダイバーの講習を受けさせました。
結果、PADIの基準以内でダイビングしたにも関わらず、娘は減圧症を起こしました。
以下、減圧症を起こした経緯と治療内容を記述します。

■海洋実習のログ(1日目)
学科試験に合格し、2011/09/10(土)に神奈川県小田原市石橋で海洋実習を受けました。
以下、ダイビングログですが、PADIの基準に収まる正しいダイビングが行われていたようです。

◆1本目
気温32℃、水温25~27℃
最大水深12m、平均水深8m
潜水時間30分、エア消費190→70気圧
◆休憩
72分
◆2本目
気温32℃、水温26~27℃
最大水深12m、平均水深5m
潜水時間36分、エア消費190→50気圧
最後に水深3mで6分間遊ぶ(安全停止)


■海洋実習終了直後(1日目)
2本目終了後、2時間後に耳の激痛を訴え、救急車で東海大学医学部付属病院に運ばれ、「急性中耳炎」と診断されました。
またそのころから足のシビレと痛みを訴え始めたため、救急科の医師さんより「減圧症」を疑われたのですが、上記ログを見せると「減圧症は考えにくいので様子を見よう」となり、そのまま帰宅しました。

■三保耳鼻咽喉科で受診(2日目)
◆2011/09/11(日)
潜水医学に詳しいという三保耳鼻咽喉科で、耳を診てもらいました。
診断結果は以下です。
  • 鼻の細菌が耳抜きにより、中耳に押し込まれ、急性中耳炎になったと考えられる。全治約1週間。
  • 急性中耳炎の後に、滲出性中耳炎(聞こえは悪いけど痛くない状態)が続き、全治3~4週間。
  • 滲出性中耳炎は、潜水医学を知らない近所の耳鼻科でも治療可能。
  • 耳抜きが悪かった時に起こるような内出血(中耳気圧外傷)は見当たらず、耳抜きはうまくできていた模様。
当時、足のシビレと痛みもあったのですが、様子見でよいだろうと親は考え、その先生に相談しませんでした。後になってみると、その時に相談すればよかったと後悔しています。

■みなと赤十字病院(3~8日目)

◆2011/09/12(月)
娘が足のシビレと痛みを強く訴えるようになり、学校にも歩いて行きたがらない様子だったので、みなと赤十字病院の小児科で診てもらいました。
触診で異常が見当たらず。重いタンクを背負ったことによる脊椎の損傷か?とのこと。

◆2011/09/13(火)
MRIで脊椎周りを撮影。

◆2011/09/16(金) 午前
MRIの結果、異常は見当たらず。救急科に回され、受診するも不明。神経内科を呼んでも不明。
救急科より、潜水医学に詳しい東海大学医学部付属病院に問い合わせていただきました。

  • 減圧症は確定診断ができない(レントゲンや触診などでは減圧症を特定できない)ので、特殊な治療を受けて経過を見るしかない
  • 急性中耳炎よりも減圧症の治療を優先するべき
上記理由により、高圧酸素治療(高気圧酸素療法)の設備を備えている近くの横浜労災病院に行くように勧められました。
もし地理的に東海大学医学部付属病院に近ければ、そこを勧められていたと思われます。

■横浜労災病院(7日後~8日後)
◆2011/09/16(金) 午後
紹介状を持参して、横浜労災病院に移動し、救急科で受診しました。
やはり触診で異常が見当たらず減圧症を疑うしかないとのことで、高気圧酸素治療を受けるために、耳鼻科で両耳の鼓膜切開の手術を受けました。
鼓膜切開は10分間の麻酔の後に、穴をあける程度の簡単な手術ですが、数日~1週間くらい後に鼓膜は塞がるとのことです。
その後、神経内科の先生による触診により「減圧症の疑いが非常に濃厚」と初めて断言されました。

また高圧酸素治療の技師より「潜水水深や時間には問題は見られないが、タンクの空気の消費量が多すぎるのが気になる」という指摘も受けました。

◆2011/09/16(金) 夜
19:30~1:30の6時間、高圧酸素治療を受けました。
チャンバーといわれる特殊な設備の中に入って、1.8気圧に上げて20分間、0.9気圧に下げた状態で5分間酸素呼吸というのを何セットも繰り返す治療です。
治療後、娘に聞いてみると、「足のシビレと痛みを10段階で表すなら、治療前後で10→1に減った」とのことで、効果はありました。
ただ、深夜に治療が完了し、娘も眠い状態なので、翌朝に来るように言われました。

◆2011/09/17(土) 午前
当日は娘の小学校の運動会だったのですが、欠席し、横浜労災病院に向かいました。
神経内科の先生の問診および触診により「治療の効果は見られる。少し痛みが残っているが自然治癒されるだろう。でも再発防止のために1回高圧酸素治療を受けてください」と。

◆2011/09/17(土) 午後
2回目の高圧酸素治療を受け、治療後はすっかりシビレも痛みも消え去ったようです。
治療は完了の方向だが、念のために翌水曜日に再来院してくださいとのこと。

■高圧酸素治療(高気圧酸素療法)
高圧酸素治療は特に痛みなどを伴う治療ではありませんが、閉所に閉じ籠る治療なので、閉所恐怖症などを抱える方は治療を受けられないようです。

突発性難聴の患者さんの治療にも高圧酸素治療がよく使われるようです。

突発性難聴などの場合、高圧酸素療法による治療は2時間前後ですが、減圧症の場合は、4~6時間前後と長くなります。
病院関係者の話では、通常、減圧症の患者は「エア切れによる緊急浮上」「ダイビング終了後の安全停止ミス」「ダイビング後の高所移動」など明らかな理由があり、すぐ高圧酸素治療を受けるとのことです。

しかし今回の娘の場合はそのような明確な理由がなく、減圧症の疑いが強まるまでに時間を要してしまいました。症状が軽かったとはいえ、早期に高圧酸素治療を受ければ1回で済んだだろうとのことでした。
なお高圧酸素治療は保険が効きますが、それでも1回で25,000円もします。
ちなみに神奈川県で、高圧酸素治療の設備(チャンバー)を有する病院は以下です。
  • 東海大学医学部付属病院
  • 横浜労災病院
  • 北里病院

■あとがき
減圧症にかかった後は、次の点に留意する必要があるとのことです。
  • 3ヶ月間は、呼吸が乱れるような激しい運動は禁止。高所移動も禁止。
  • 6カ月~1年間は、ダイビングは見合わせること。
幸運にも娘は、急性中耳炎と減圧症を同時に患いながらも、海に対する情熱が冷めていないようで、親としてはその点では安堵しています。再発防止のために、現在、ダイビングのインストラクターおよびPADIにもエスカレートして相談しているところです。
最後になりますが、緊急の手話通訳派遣依頼にも応えてくださった横浜市聴覚障害者情報提供施設の方々、手話通訳の方々、丁寧にご説明してくださった医療関係者の皆さま、感謝の気持ちでいっぱいです。ありがとうございました。

2011/09/04

聴覚障害教育これまでとこれから

私は乳児~高校時代まで(2つの異なる)ろう学校で育ち、聾学校の先生を目指した時期があって、中学~高校の教員免許状を持っています。

結局、就職活動を始める直前にソフトウェア開発が本当に好きだという自分に目覚めて教員にはならなかったのですが、今でもろう教育の状況はどうなってるんだろう、ろう学校の教員になった何人かの知人・友人たちは元気で頑張っているだろうかと、ふと気になることがあります。

また難聴児を持つ親御さんからも相談を受けることがあるのですが、たいていはうまくアドバイスできないんですよね。それで少しでも何か知っておきたいということで、知人に次の本を紹介してもらいました。

聴覚障害教育これまでとこれから―コミュニケーション論争・9歳の壁・障害認識を中心に
本の内容は知人のブログ「聴覚障害教育これまでとこれから」でよくまとめておられますが、私も読書して面白かったです。著者の脇中さんのご姿勢にも好感を抱き、特にBICSとCALPは勉強になりました。私はろう教育も含め何もかも詳しくないのですが、素人なりに感想を述べます。

脇中さんのように率直で、優れた自己表現をされる教員が、昔の聴覚口話法に疑問を持たれつつ、「日本手話と対応手話を区別する必要性を強く感じていない」としているところに「惜しいなぁ!」と感じました。私なら「日本手話と対応手話をしっかり区別し、双方の観点からのアプローチを考える」としたいなぁと。

断続的な引用になりますが、次の文章がありました(カッコ内は私の補足)。

音韻意識の形成のために2つの手話を包含する新しい方向性の追求が必要であると考えます。

日本語で考える習慣のない子どもは、習慣のある子どもと比べると、日本語の定着が難しいと思われるので、手話の早期導入を図りながらも、「(日本語の)音韻意識」の形成や定着に留意する必要があると考えます。

上記の考え方には、やはり違和感や疑問が湧き出てくるのを禁じ得ません。なぜなら、私の知る限り、日本語で考える習慣(日本語の音韻意識)が弱い子どもは、中・高校生以降でもその習慣が向上した例を見たことがないからです。逆に言うと、日本語の音韻意識が優れている子どもは、幼い時から優れていました。

今度は、自分を例に挙げます。幼い時期から日本語が正しく書けていて教育関係者などを驚かせたものですが、実は大学入学前までの私の自己表現力は-目を覆いたくなるほどではないが-ひどいものでした。こんな私でも、大学入学後に、このブログをご覧のように人並みの文章が書けるようになり、両親を驚かせたのも事実です。読書も作文も好きではない私が、なぜここまで改善できたのでしょうか。大学入学を機に、否応(いやおう)でも、日本語または手話を使って人に説明しなければならない機会が増え、推敲する時間が増えたからではないかと思うのです。

一方、学生時代も含め、日本語を含めた「学力」が私と同程度か少し劣っていても、私が足元に及ばないくらい優れた自己表現ができる方々が結構いらっしゃるのです!また日本語がマジョリティ言語とはいえ、日本語が苦手でもご本人の適応力によって、同僚が手話を覚えてくれ、楽しい職場生活を送られた例もあるのです。そのような方々のことも考えなければならないように思います。

以上、回り道をしましたが、

日本手話を用いた教育の場面において、日本語のCALPが育つかどうかを脇中さんは懸念されています。でも逆に、対応手話を使って音韻意識を形成させる試みも、多くの子どもにとっては上達する可能性がすこぶる低いのではないかと、私の経験から来る直感が感じています。(直感でゴメンなさい・・・)

つまり、日本語にしろ手話にしろ自己表現力の切磋琢磨は、ご本人の思考、推敲、あるいは、練習の蓄積によるものがあると思うのです。すなわち考えたり感じたりする力です。

子どもに合わせた教育を実践できるのが望ましいのですが、現実的に学校のようなクラス制では個別教育ができる部分があまりないだろうと思います。

考えたり感じたりする力の蓄積を、日本手話で育む子どもがいれば対応手話で育む子どももいると思うのですが、うまくいかなかった場合の人格形成のリスクを考えると、やはり後者の方が懸念されるべきではないかと警鐘を鳴らし続けていきたいです。

2011/06/21

Java HotSpot Garbage Collectionの翻訳

Java HotSpot Garbage Collectionを翻訳しました。

第1章   The Garbage-First Garbage Collector

The Garbage-First Garbage Collector (or G1 GC for short) is a new GC that is being introduced in the Java HotSpot VM in JDK 7. An experimental version of G1 has also been released in Java SE 6 Update 14. G1 is the long-term replacement for HotSpot's low-latency Concurrent Mark-Sweep GC (widely referred to as CMS).
Garbage-First Garbage Collector (短縮名:G1 GC)は、JDK 7Java HotSpot VMで導入される新しいGCです。実験バージョンのG1は、Java SE 6 Update 14でもリリースされました。G1は、HotSpotの低レイテンシ性のConcurrent Mark-Sweep GC(広くCMSと呼ばれている)に、長期的に置き換わるものです。

1.1.  Attributes (属性)

G1 is a “server-style” GC and has the following attributes.
G1はサーバ向けGCで、次の属性を持ちます。

Parallelism and Concurrency. G1 takes advantage of the parallelism that exists in hardware today. It uses all available CPUs (cores, hardware threads, etc.) to speed up its “stop-the-world” pauses when an application's Java threads are stopped to enable GC. It also works concurrently with running Java threads to minimize whole-heap operations during stop-the-world pauses.
並行と並列性:G1は最新のハードウェアに搭載されている並行性を利用します。G1はすべての利用可能なCPU(コアやハードウェア・スレッドなど)を使用し、GC実行時にアプリケーションのJavaスレッドがすべて停止される「stop-the-world」を短縮化します。G1は、stop-the-world中の全ヒープに対する操作を最小限にするために、複数のJavaスレッド上で並列に動作します。

Generational. Like the other HotSpot GC's, G1 is generational, meaning it treats newly-allocated (aka young) objects and objects that have lived for some time (aka old) differently. It concentrates garbage collection activity on young objects, as they are the ones most likely to be reclaimable, while visiting old objects infrequently. For most Java applications, generational garbage collection has major efficiency advantages over alternative schemes.
世代:他のHotSpot GCのように、G1も世代別に動作します。新しく割り当てられたばかりの若いオブジェクトと、生存期間の長い古いオブジェクトを別々に扱います。G1は、まれに古いオブジェクトを監視すると同時に、若いオブジェクトに対するガーベジコレクション活動に集中します。なぜなら、若いオブジェクトのほとんどが回収されるからです。
世代別ガーベジコレクションは、ほとんどのJavaアプリケーションにおいては、他の方法に勝る大きな効果をもたらします。

Compaction. Unlike CMS, G1 performs heap compaction over time. Compaction eliminates potential fragmentation problems to ensure smooth and consistent long-running operation.
コンパクション(圧縮):CMSと違って、G1は常にヒープ圧縮を行います。コンパクションは、長時間にわたって円滑かつ調和がとれた運用ができるように、潜在的な断片化問題を除去します。

Predictability. G1 is expected to be more predictable than CMS. This is largely due to the elimination of fragmentation issues that can negatively affect stop-the-world pause times in CMS. Additionally, G1 has a pause prediction model that, in many situations, allows it to often meet (or rarely exceed) a pause time target.
予測:G1CMSよりも予測可能であることが期待されています。これは、CMSでのstop-the-worldに悪影響を与える断片化問題の除去による効果が大きいです。くわえて、G1は停止予測モデルを持っています。停止予測モデルは、さまざまな状況においてG1が停止時間目標に常に対応できる(または、停止時間目標を滅多に超過しない)ようにします。

1.2.  Description(解説)

G1 takes a very different approach to heap layout than other HotSpot GCs. In G1, there is no physical separation between the young and old generations. Instead, there is a single contiguous heap which is split into same-sized regions. The young generation is a set of potentially non-contiguous regions, and the same is true for the old generation. This allows G1 to flexibly move resources as needed from the old to the young generation, and vice versa.
G1のヒープ設計に対する取り組みは、他のHotSpot GCとは異なります。G1では、Young世代領域とOld世代領域を物理的に分離しません。その代わりに、1つの連続したヒープ領域を、同サイズの領域に分離します。Young世代領域は、一組の非連続領域の場合もあります。Old世代領域も同様です。これは、G1が必要に応じてOld世代領域からYoung世代領域(逆方向も含む)へのリソース移動が柔軟にできるようにするためです。

Collection in G1 takes place through evacuation pauses, during which the survivors from regions referred to as the collection set are evacuated to another set of regions (the to-space) so that the collection set regions can then be reclaimed. Evacuation pauses are done in parallel, with all available CPUs participating. Most evacuation pauses collect the available young regions, thus are the equivalent of young collections in other HotSpot GCs. Occasionally, select old regions may also be collected during these pauses because G1 piggybacks old generation collection activity on young collections.
「退避停止」の間に、G1のコレクションが発生します。退避停止とは、生存オブジェクトが、「コレクションセット」と呼ばれる領域から、片方の領域(To領域)に退避される間ずっと停止されるものです。退避停止の目的は、コレクションセット領域を回復させるためです。退避停止は、すべての利用可能なCPUを利用して、パラレル(並列)に実行されます。ほとんどの退避停止では、利用可能なYoung世代領域に対して、他のHotSpot GCYoung世代領域に対するコレクションと同様に、コレクションを実行します。時々、選ばれたOld世代領域に対しても、コレクションが実行される場合があります。これはG1Young世代領域に対するコレクション活動にOld世代領域に対するコレクション活動を乗せているためです(これを「ビギーバック方式」という)。

Like CMS, G1 periodically performs a concurrent marking phase. The main role of this phase is to identify which old regions are mostly full of garbage objects, as these are the most efficient and desirable to collect. Unlike CMS, G1 does not perform a concurrent sweeping phase. Instead, the most profitable old regions identified by the concurrent marking phase are collected during subsequent evacuation pauses (the piggybacking mentioned above).
CMSと同様に、G1は、周期的にコンカレント・マーキング・フェーズを実行します。このフェーズの主な役割は、どのOld世代領域が不要オブジェクト(ゴミ)で一杯になっているかを特定することです。これはコレクションを実行するのに、効率的で望ましいことだからです。CMSと違って、G1はコンカレント・スィーピング・フェーズを実行しません。その代わり、コンカレント・マーキング・フェーズで特定されたOld世代領域に対して、続いて発生する退避停止の間に、コレクションを実行します(前述のビギーバッキング方式)。

1.3.  Using G1G1の使用にあたって)

G1 is still considered experimental and can be enabled with the following two parameters:
G1はまだ実験段階ですが、次の2つのオプションで有効にすることができます。


-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC


To set a GC pause time goal, use the following parameter:
GC停止時間の最大目標値を設定するには、次のオプションを使用します。


-XX:MaxGCPauseMillis=50  (for a pause time target of 50ms)(停止時間目標を50ミリ秒に設定)


With G1, a time interval can be specified during which a GC pause should last no longer than the time given above:
次のオプションで、GC停止を合計で MaxGCPauseMillisミリ秒 まで許可するようにインターバル時間を設定することができます。


-XX:GCPauseIntervalMillis =200  (for a pause interval target of 200ms)(停止感覚目標を200ミリ秒に設定)


Note that the above two options represent goals, not promises or guarantees. They might work well in some situations but not in others, and the GC might not always be able to obey them.
注意:上の2つのオプションは、目標を設定するものであって、設定されたとおりに動作することを約束するものでも保障するものでもありません。いくつかの場面では設定された通りに動作しますが、そうでない場面もあります。CGは必ずしもそれらの設定値に従うことができるわけではありません。

Alternatively, the size of the young generation can be specified explicitly to impact evacuation pause times:
あるいは、退避停止時間を短縮させるために、Young世代領域のサイズを明示的に指定することもできます。


-XX:+G1YoungGenSize=512m (for a 512 megabyte young generation)Young世代領域を512MBに設定)


G1 also uses the equivalent of survivor spaces, which are, naturally, a set of (potentially non-contiguous) regions. Their size can be specified with the usual parameters (e.g., -XX:SurvivorRatio=6).
G1も他のGCと同様にSurvivor領域を使用します。Survivor領域は従来とおりの一組の領域で、非連続領域の場合もあります。これらのサイズは、従来と同じオプションで設定することができます(例:-XX:SurvivorRatio=6

Finally, to run G1 at its full potential, try setting these two parameters which are currently disabled by default because they may uncover a rare race condition:
最終的にこれらの2つのオプションを設定してみて、最大限の可能性でG1を実行してください。これらの2つのオプションは、まれに競合する可能性があるため、デフォルトでは無効になっています。


-XX:+G1ParallelRSetUpdatingEnabled -XX:+G1ParallelRSetScanningEnabled


One more thing to note is that G1 is very verbose compared to other HotSpot GCs when -XX:+PrintGCDetails is set. This is because it prints per-GC-thread timings and other information very helpful in profiling and trouble-shooting. If you want a more concise GC log, please switch to using -verbosegc (though it is recommended that the more detailed GC log be obtained).
注意:G1は、"-XX:+PrintGCDetails"オプションが設定されてるとき、他のHotSpot GCに比べてログ出力が冗長になります。これはGCのスレッドごとに、プロファイリングやトラブルシューティングに有用なタイミングや他の情報を出力するためです。出力を簡潔にしたい場合は、"-verbose"オプションを使ってください。ただし、詳細なGCログを出力することを推奨します。

1.4.  Status(事情)

G1 development is now focused primarily on resolving any remaining reliability issues and improving performance, and also on removing the following limitations:
G1の開発は、未解決の信頼性問題の解決、性能の改良に主に焦点を合わせています。また、次の制限事項の解除にも焦点を合わせています。

l   G1 does not fully support the JVM Tool Interface (JVM TI) or Java Management Extensions (JMX), so it is likely that monitoring and management tools will not work correctly with G1.
G1
JVM Tool InterfaceJVMTI)とJava Management ExtensionsJMX)を完全にサポートしていません。したがって、G1使用時は、モニタリングやマネジメントを行うツールは正常に動作しません。(訳注:JConsoleなどはG1GCに未対応ということ)
l   G1 does not support incremental permanent generation collection. If an application does a lot of class unloading and requires permanent generation collection, this will be done during Full GCs.
G1
Permanent世代領域に対するインクリメント機能をサポートしていません。なお、アプリケーションが大量のクラスをアンロードし、Permanent世代領域に対するコレクションが必要となった場合に発生するFull GCにおいて、インクリメントが実行されます。
l   In terms of GC pause times, G1 is sometimes better and sometimes worse than CMS. Work is ongoing to make G1 consistently as good as, if not better than, CMS.
GC
停止時間に関しては、G1は時々CMSよりよく、時々CMSより悪いです。G1CMSよりよくない場合は、首尾一貫してCMSと同じくらいになるように、開発を行っています。

1.5.  Resources(リソース)

l   Description of HotSpot GCs: Memory Management in the Java HotSpot Virtual Machine White Paper: http://java.sun.com/j2se/reference/whitepapers/memorymanagement_whitepaper.pdf
l   The original CMS paper: Printezis, T. and Detlefs, D. 2000. A generational mostly-concurrent garbage collector. In Proceedings of the 2nd international Symposium on Memory Management (Minneapolis, Minnesota, United States, October 15 - 16, 2000). http://portal.acm.org/citation.cfm?id=362422.362480 (requires access to ACM's portal)
l   The original G1 paper: Detlefs, D., Flood, C., Heller, S., and Printezis, T. 2004. Garbage-first garbage collection. In Proceedings of the 4th international Symposium on Memory Management (Vancouver, BC, Canada, October 24 - 25, 2004). http://portal.acm.org/citation.cfm?id=1029879 (requires access to ACM's portal)
l   G1 talk from JavaOne 2008: http://developers.sun.com/learning/javaoneonline/j1sessn.jsp?sessn=TS-5419&yr=2008