2009-09-02 1 views
9

私は小さなIOクラスを実装しました。これは、複数の同じディスク上の同じファイル(たとえば、同じファイルを含む2つのハードディスク)から読み取ることができます。シーケンシャルな場合、両方のディスクは平均60MB/sのファイルを読み込みますが、インターリーブすると(4kディスク1,4kディスク2を結合すると)、実効読み取り速度は40MB/sに低下します。インターリーブされたパラレルファイルはシーケンシャルリードよりも遅く読み込まれますか?

コンテキスト:Win 7 + JDK 7b70、2GB RAM、2.2GBテストファイル。基本的に、私はWin7のReadyBoostとRAID xを貧しい人の方法で模倣しようとしています。

クラスにread()が発行されると、特定の位置と長さから事前に開いたRandomAccessFileを読み込む命令を持つ2つのランナブルが作成されます。 ExecutorサービスとFuture.get()呼び出しを使用すると、両方が終了すると、読み取られたデータは共通のバッファにコピーされ、呼び出し元に返されます。

私のアプローチには構想上の誤りはありますか? (例えば、OSのキャッシング機構は、常に対抗だろうか?)

protected <T> List<T> waitForAll(List<Future<T>> futures) 
throws MultiIOException { 
    MultiIOException mex = null; 
    int i = 0; 
    List<T> result = new ArrayList<T>(futures.size()); 
    for (Future<T> f : futures) { 
     try { 
      result.add(f.get()); 
     } catch (InterruptedException ex) { 
      if (mex == null) { 
       mex = new MultiIOException(); 
      } 
      mex.exceptions.add(new ExceptionPair(metrics[i].file, ex)); 
     } catch (ExecutionException ex) { 
      if (mex == null) { 
       mex = new MultiIOException(); 
      } 
      mex.exceptions.add(new ExceptionPair(metrics[i].file, ex)); 
     } 
     i++; 
    } 
    if (mex != null) { 
     throw mex; 
    } 
    return result; 
} 

public int read(long position, byte[] output, int start, int length) 
throws IOException { 
    if (start < 0 || start + length > output.length) { 
     throw new IndexOutOfBoundsException(
     String.format("start=%d, length=%d, output=%d", 
     start, length, output.length)); 
    } 
    // compute the fragment sizes and positions 
    int result = 0; 
    final long[] positions = new long[metrics.length]; 
    final int[] lengths = new int[metrics.length]; 
    double speedSum = 0.0; 
    double maxValue = 0.0; 
    int maxIndex = 0; 
    for (int i = 0; i < metrics.length; i++) { 
     speedSum += metrics[i].readSpeed; 
     if (metrics[i].readSpeed > maxValue) { 
      maxValue = metrics[i].readSpeed; 
      maxIndex = i; 
     } 
    } 
    // adjust read lengths 
    int lengthSum = length; 
    for (int i = 0; i < metrics.length; i++) { 
     int len = (int)Math.ceil(length * metrics[i].readSpeed/speedSum); 
     lengths[i] = (len > lengthSum) ? lengthSum : len; 
     lengthSum -= lengths[i]; 
    } 
    if (lengthSum > 0) { 
     lengths[maxIndex] += lengthSum; 
    } 
    // adjust read positions 
    long positionDelta = position; 
    for (int i = 0; i < metrics.length; i++) { 
     positions[i] = positionDelta; 
     positionDelta += (long)lengths[i]; 
    }   
    List<Future<byte[]>> futures = new LinkedList<Future<byte[]>>(); 
    // read in parallel 
    for (int i = 0; i < metrics.length; i++) { 
     final int j = i; 
     futures.add(exec.submit(new Callable<byte[]>() { 
      @Override 
      public byte[] call() throws Exception { 
       byte[] buffer = new byte[lengths[j]]; 
       long t = System.nanoTime(); 
       long t0 = t; 

       long currPos = metrics[j].handle.getFilePointer(); 
       metrics[j].handle.seek(positions[j]); 
       t = System.nanoTime() - t; 
       metrics[j].seekTime = t * 1024.0 * 1024.0/
        Math.abs(currPos - positions[j])/1E9 ; 

       int c = metrics[j].handle.read(buffer); 
       t0 = System.nanoTime() - t0; 
       // adjust the read speed if we read something 
       if (c > 0) { 
        metrics[j].readSpeed = (alpha * c * 1E9/t0/1024/1024 
        + (1 - alpha) * metrics[j].readSpeed) ; 
       } 
       if (c < 0) { 
        return null; 
       } else 
       if (c == 0) { 
        return EMPTY_BYTE_ARRAY; 
       } else 
       if (c < buffer.length) { 
        return Arrays.copyOf(buffer, c); 
       } 
       return buffer; 
      } 
     })); 
    } 
    List<byte[]> data = waitForAll(futures); 
    boolean eof = true; 
    for (byte[] b : data) { 
     if (b != null && b.length > 0) { 
      System.arraycopy(b, 0, output, start + result, b.length); 
      result += b.length; 
      eof = false; 
     } else { 
      break; // the rest probably reached EOF 
     } 
    } 
    // if there was no data at all, we reached the end of file 
    if (eof) { 
     return -1; 
    } 
    sequentialPosition = position + (long)result; 

    // evaluate the fastest file to read 
    double maxSpeed = 0; 
    maxIndex = 0; 
    for (int i = 0; i < metrics.length; i++) { 
     if (metrics[i].readSpeed > maxSpeed) { 
      maxSpeed = metrics[i].readSpeed; 
      maxIndex = i; 
     } 
    } 
    fastest = metrics[maxIndex]; 
    return result; 
} 

(指標アレイ内FileMetricsが適応各種入力チャネルのバッファサイズを決定するために、読み出し速度の測定値を含む - アルファ= 0とreadSpeedと私の試験に= 1つの結果平等な分配)

編集 私は。例えば、別のスレッドで独立して2つのファイルを読み込む(非もつれテストを実行しました)、私は110メガバイト/秒の組み合わせ実効速度を持っています。

編集2 私はなぜこれが起こっているのか知っていますね。

私は並行して読み込みを行うと、ディスクのシーケンシャルリードではなく、インターリーブのためにread-skip-read-skipパターンが発生します(おそらくアロケーションテーブルのルックアップに悩まされる可能性があります)。これは基本的に、1ディスクあたりの有効な読み取り速度を半減させます。

+0

これは興味深い問題であり、解決策を見つけるのに良いことです。私は答えとして解決策を書いて、あなた自身の答えを受け入れるべきだと思います。 – Guss

答えて

3

あなたが言ったように、ディスク上のシーケンシャルリードは、リードスキップリードスキップパターンよりもはるかに高速です。ハードディスクは、連続して読み出すと高帯域幅が可能ですが、シーク時間(レイテンシ)は高価です。

各ディスクにファイルのコピーを保存する代わりに、ファイルのブロックiをディスクi(mod 2)に保存してみてください。この方法で、両方のディスクから順番に読み取り、結果をメモリに再結合することができます。

+0

それは私の考えでもありました。 – akarnokd

0

ディスクごとに複数の読み取りを実行することが確実な場合(それ以外の場合はディスクミスが多い)、コンピュータ内の他の部分、バス、RAIDコントローラ(存在する場合)、そうです。

+0

いいえ、バス競合の場合はそうではありません。 – akarnokd

2

並列読み取りを実行する場合は、読み取りを2回の連続読み取りに分割します。中間点を見つけて、最初のファイルから前半を読み込み、2番目のファイルから後半を読み込みます。

+0

ありがとう、私は既に基本的な問題を考え直し、速度向上を達成するためのより良い方法を見つけました。 – akarnokd

関連する問題