java performance stream nio

¿Por qué el java DirectoryStream funciona tan lento?



performance nio (1)

Files.list () es una operación O (N) mientras que la ordenación es O (N log N). Es mucho más probable que las operaciones dentro de la clasificación que importan. Dadas las comparaciones, no hagas lo mismo, esta es la explicación más probable. Hay muchos archivos con la misma fecha de modificación en C: / Windows / System32, lo que significa que el tamaño se comprobará con bastante frecuencia.

Para mostrar que la mayor parte del tiempo no se gasta en FIles.list (dir) Stream, he optimizado la comparación para que los datos sobre un archivo solo se obtengan una vez por archivo.

import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class FileSorter { // This sorts from old to young and from big to small Comparator<Path> timeSizeComparator = (Path o1, Path o2) -> { int sorter = 0; try { FileTime lm1 = Files.getLastModifiedTime(o1); FileTime lm2 = Files.getLastModifiedTime(o2); if (lm2.compareTo(lm1) == 0) { Long s1 = Files.size(o1); Long s2 = Files.size(o2); sorter = s2.compareTo(s1); } else { sorter = lm1.compareTo(lm2); } } catch (IOException ex) { throw new UncheckedIOException(ex); } return sorter; }; public String[] getSortedFileListAsArray(Path dir) throws IOException { Stream<Path> stream = Files.list(dir); return stream.sorted(timeSizeComparator). map(Path::getFileName).map(Path::toString).toArray(String[]::new); } public List<String> getSortedFileListAsList(Path dir) throws IOException { Stream<Path> stream = Files.list(dir); return stream.sorted(timeSizeComparator). map(Path::getFileName).map(Path::toString).collect(Collectors. toList()); } public String[] sortByDateAndSize(File[] fileList) { Arrays.sort(fileList, (File o1, File o2) -> { int r = Long.compare(o1.lastModified(), o2.lastModified()); if (r != 0) { return r; } return Long.compare(o1.length(), o2.length()); }); String[] fileNames = new String[fileList.length]; for (int i = 0; i < fileNames.length; i++) { fileNames[i] = fileList[i].getName(); } return fileNames; } public List<String> getSortedFile(Path dir) throws IOException { return Files.list(dir).map(PathInfo::new).sorted().map(p -> p.getFileName()).collect(Collectors.toList()); } static class PathInfo implements Comparable<PathInfo> { private final String fileName; private final long modified; private final long size; public PathInfo(Path path) { try { fileName = path.getFileName().toString(); modified = Files.getLastModifiedTime(path).toMillis(); size = Files.size(path); } catch (IOException ex) { throw new UncheckedIOException(ex); } } @Override public int compareTo(PathInfo o) { int cmp = Long.compare(modified, o.modified); if (cmp == 0) cmp = Long.compare(size, o.size); return cmp; } public String getFileName() { return fileName; } } public static void main(String[] args) throws IOException { // File (io package) File f = new File("C://Windows//system32"); // Path (nio package) Path dir = Paths.get("C://Windows//system32"); FileSorter fs = new FileSorter(); long before = System.currentTimeMillis(); String[] names = fs.sortByDateAndSize(f.listFiles()); long after = System.currentTimeMillis(); System.out.println("Run time of Arrays.sort: " + ((after - before))); long before2 = System.currentTimeMillis(); String[] names2 = fs.getSortedFileListAsArray(dir); long after2 = System.currentTimeMillis(); System.out.println("Run time of Stream.sorted as Array: " + ((after2 - before2))); long before3 = System.currentTimeMillis(); List<String> names3 = fs.getSortedFileListAsList(dir); long after3 = System.currentTimeMillis(); System.out.println("Run time of Stream.sorted as List: " + ((after3 - before3))); long before4 = System.currentTimeMillis(); List<String> names4 = fs.getSortedFile(dir); long after4 = System.currentTimeMillis(); System.out.println("Run time of Stream.sorted as List with caching: " + ((after4 - before4))); } }

Esto se imprime en mi computadora portátil.

Run time of Arrays.sort: 1980 Run time of Stream.sorted as Array: 1295 Run time of Stream.sorted as List: 1228 Run time of Stream.sorted as List with caching: 185

Como puede ver, aproximadamente el 85% del tiempo se usa para obtener la fecha y el tamaño de modificación de los archivos repetidamente.

He hecho algunas pruebas con Streams en especial con DirectoryStreams del paquete nio. Simplemente trato de obtener una lista de todos los archivos en un directorio ordenados por fecha y tamaño de la última modificación.

El JavaDoc de File.listFiles () anterior establece una Nota para el método en File s :

Tenga en cuenta que la clase Files define el método newDirectoryStream para abrir un directorio e iterar sobre los nombres de los archivos en el directorio. Esto puede usar menos recursos cuando se trabaja con directorios muy grandes.

Ejecuto el código a continuación muchas veces (primero tres veces más abajo):

Primer intento:

Run time of Arrays.sort: 1516 Run time of Stream.sorted as Array: 2912 Run time of Stream.sorted as List: 2875

Segundo intento:

Run time of Arrays.sort: 1557 Run time of Stream.sorted as Array: 2978 Run time of Stream.sorted as List: 2937

Tercera ejecución:

Run time of Arrays.sort: 1563 Run time of Stream.sorted as Array: 2919 Run time of Stream.sorted as List: 2896

Mi pregunta es: ¿Por qué las transmisiones funcionan tan mal?

import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class FileSorter { // This sorts from old to young and from big to small Comparator<Path> timeSizeComparator = (Path o1, Path o2) -> { int sorter = 0; try { FileTime lm1 = Files.getLastModifiedTime(o1); FileTime lm2 = Files.getLastModifiedTime(o2); if (lm2.compareTo(lm1) == 0) { Long s1 = Files.size(o1); Long s2 = Files.size(o2); sorter = s2.compareTo(s1); } else { sorter = lm1.compareTo(lm2); } } catch (IOException ex) { throw new UncheckedIOException(ex); } return sorter; }; public String[] getSortedFileListAsArray(Path dir) throws IOException { Stream<Path> stream = Files.list(dir); return stream.sorted(timeSizeComparator). map(Path::getFileName).map(Path::toString).toArray(String[]::new); } public List<String> getSortedFileListAsList(Path dir) throws IOException { Stream<Path> stream = Files.list(dir); return stream.sorted(timeSizeComparator). map(Path::getFileName).map(Path::toString).collect(Collectors. toList()); } public String[] sortByDateAndSize(File[] fileList) { Arrays.sort(fileList, (File o1, File o2) -> { int r = Long.compare(o1.lastModified(), o2.lastModified()); if (r != 0) { return r; } return Long.compare(o1.length(), o2.length()); }); String[] fileNames = new String[fileList.length]; for (int i = 0; i < fileNames.length; i++) { fileNames[i] = fileList[i].getName(); } return fileNames; } public static void main(String[] args) throws IOException { // File (io package) File f = new File("C://Windows//system32"); // Path (nio package) Path dir = Paths.get("C://Windows//system32"); FileSorter fs = new FileSorter(); long before = System.currentTimeMillis(); String[] names = fs.sortByDateAndSize(f.listFiles()); long after = System.currentTimeMillis(); System.out.println("Run time of Arrays.sort: " + ((after - before))); long before2 = System.currentTimeMillis(); String[] names2 = fs.getSortedFileListAsArray(dir); long after2 = System.currentTimeMillis(); System.out. println("Run time of Stream.sorted as Array: " + ((after2 - before2))); long before3 = System.currentTimeMillis(); List<String> names3 = fs.getSortedFileListAsList(dir); long after3 = System.currentTimeMillis(); System.out. println("Run time of Stream.sorted as List: " + ((after3 - before3))); } }

Actualizar

Después de aplicar el código de Peter, tengo los siguientes resultados:

Run time of Arrays.sort: 1615 Run time of Stream.sorted as Array: 3116 Run time of Stream.sorted as List: 3059 Run time of Stream.sorted as List with caching: 378

Actualización 2

Después de investigar un poco sobre la solución de Peter, puedo decir que los atributos del archivo de lectura son por ej. Files.getLastModified debe ser un problema pesado. Cambiando solo esa parte en el Comparador a:

Comparator<Path> timeSizeComparator = (Path o1, Path o2) -> { File f1 = o1.toFile(); File f2 = o2.toFile(); long lm1 = f1.lastModified(); long lm2 = f2.lastModified(); int cmp = Long.compare(lm2, lm1); if (cmp == 0) { cmp = Long.compare(f2.length(), f1.length()); } return cmp; };

Obtiene el mejor resultado en mi computadora:

Run time of Arrays.sort: 1968 Run time of Stream.sorted as Array: 1999 Run time of Stream.sorted as List: 1975 Run time of Stream.sorted as List with caching: 488

Pero como puede ver, el almacenamiento en caché del objeto es la mejor manera. Y como mencionó jtahlborn, es una especie de género estable.

Actualización 3 (la mejor solución que he encontrado)

Después de investigar un poco más, he visto que los métodos Files.lastModified y Files.size, ambos hacen un gran trabajo en una misma cosa: Atributos. Así que hice tres versiones de la clase PathInfo para probar:

  1. Versión de Peters como se describe abajo
  2. Una versión de archivo de estilo antiguo, donde hago un Path.toFile () una vez en el constructor y obtengo todos los valores de ese archivo con f.lastModified y f.length
  3. Una versión de la solución Peters, pero ahora leo un objeto Attribute con Files.readAttributes (path, BasicFileAttributes.class) y hago cosas al respecto.

Poniendo todo en un círculo para hacerlo 100 veces cada uno, se me ocurrieron estos resultados:

After doing all hundred times Mean performance of Peters solution: 432.26 Mean performance of old File solution: 343.11 Mean performance of read attribute object once solution: 255.66

Código en el constructor de PathInfo para la mejor solución:

public PathInfo(Path path) { try { // read the whole attributes once BasicFileAttributes bfa = Files.readAttributes(path, BasicFileAttributes.class); fileName = path.getFileName().toString(); modified = bfa.lastModifiedTime().toMillis(); size = bfa.size(); } catch (IOException ex) { throw new UncheckedIOException(ex); } }

Mi resultado: nunca lees los atributos dos veces y el almacenamiento en caché en un objeto está reventando.