2009-09-02 9 views
9

He implementado una pequeña clase de IO, que puede leer desde múltiples y mismos archivos en diferentes discos (por ejemplo, dos discos duros que contienen el mismo archivo). En caso secuencial, ambos discos leen 60MB/s en promedio sobre el archivo, pero cuando hago un intercalado (por ejemplo, 4k disco 1, 4k disco 2 y luego combino), la velocidad de lectura efectiva se reduce a 40MB/s en vez de aumentar?¿El archivo paralelo entrelazado lee más lento que la lectura secuencial?

Contexto: Win 7 + JDK 7b70, 2GB RAM, archivo de prueba 2.2GB. Básicamente, trato de imitar a ReadyBoost y RAID x de Win7 en la forma de un hombre pobre.

En el corazón, cuando se emite un read() para la clase, crea dos ejecutables con instrucciones para leer un RandomAccessFile pre-abierto desde una posición y longitud determinada. Usando un servicio de ejecutor y llamadas a Future.get(), cuando ambos finalizan, la lectura de datos se copia en un búfer común y se devuelve a la persona que llama.

¿Hay un error de concepción en mi enfoque? (Por ejemplo, el mecanismo de OS almacenamiento en caché siempre contrarrestará?)

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 en métricas array contienen las mediciones de velocidad de lectura para determinar de forma adaptativa los tamaños de búfer de diversos canales de entrada - en mi prueba con alfa = 0 y readSpeed = 1 resultados igual de distribución)

Editar I corrieron una prueba no enredados (por ejemplo leer los dos archivos independientemente en hilos separados.) y tengo una velocidad efectiva combinada de 110 MB/s.

Edit2 Supongo que sé por qué sucede esto.

Cuando leo en paralelo y en secuencia, no es una lectura secuencial para los discos, sino un patrón de lectura-salto-lectura-salto debido al entrelazado (y posiblemente acribillado con búsquedas en la tabla de asignación). Esto básicamente reduce la velocidad de lectura efectiva por disco a la mitad o peor.

+0

Es un problema interesante y bueno para encontrar la solución. Creo que deberías escribir la solución como una respuesta y aceptar tu propia respuesta. – Guss

Respuesta

3

Como dijiste, una lectura secuencial en un disco es mucho más rápida que un patrón de lectura-salto-lectura-salto. Los discos duros son capaces de tener un ancho de banda alto cuando se leen de forma secuencial, pero el tiempo de búsqueda (latencia) es costoso.

En lugar de guardar una copia del archivo en cada disco, intente almacenar el bloque i del archivo en el disco i (mod 2). De esta forma, puede leer en ambos discos secuencialmente y recombinar el resultado en la memoria.

+0

Esa fue mi idea también y funciona. – akarnokd

0

Si está seguro de que no realiza más de una lectura por disco (de lo contrario tendrá muchas fallas en el disco), aún crea contención en otras partes de la computadora: el bus, el controlador de banda (si existe) y pronto.

+0

No, no es el caso de la contención del autobús. – akarnokd

2

Si desea hacer una lectura en paralelo, divida la lectura en dos lecturas secuenciales. Encuentra el punto intermedio y lee la primera mitad del primer archivo y la segunda mitad del segundo archivo.

+0

Gracias, ya repensé el problema de base y encontré una mejor manera de lograr las mejoras de velocidad. – akarnokd

Cuestiones relacionadas