Многопоточность выполняется медленнее, чем один процесс

для задания в школе меня попросили создать простую программу, которая создает 1000 текстовых файлов, каждый со случайным количеством строк, подсчитайте, сколько строк есть через многопоточныйодиночный процесс. чем удалить эти файлы.

теперь странная вещь происходит во время тестирования - линейный подсчет всех файлов всегда немного быстрее, чем подсчет их в многопоточных образом, который вызвал довольно заседании ученого теоретизирования в моем классе кружок.

когда используя Scanner чтобы прочитать все файлы, все работает по назначению-1000 файлов читаются примерно в 500 мс линейного времени и 400 мс резьбового времени

но когда я использую BufferedReader раз опускаться до 110ms линейных и 130 МС резьбой.

какая часть кода вызывает это узкое место и почему?

EDIT: просто чтобы уточнить, я не спрашиваю, почему Scanner работает медленнее, чем BufferedReader.

полный компилируемый код: (хотя вы должны изменить путь создания файла вывод)

import java.io.*;
import java.util.Random;
import java.util.Scanner;

/**
 * Builds text files with random amount of lines and counts them with 
 * one process or multi-threading.
 * @author Hazir
 */// CLASS MATALA_4A START:
public class Matala_4A {

    /* Finals: */
    private static final String MSG = "Hello World";

    /* Privates: */
    private static int count;
    private static Random rand;

    /* Private Methods: */ /**
     * Increases the random generator.
     * @return The new random value.
     */
    private static synchronized int getRand() {
        return rand.nextInt(1000);
    }

    /**
     * Increments the lines-read counter by a value.
     * @param val The amount to be incremented by.
     */
    private static synchronized void incrementCount(int val) {
        count+=val;
    }

    /**
     * Sets lines-read counter to 0 and Initializes random generator 
     * by the seed - 123.
     */
    private static void Initialize() {
        count=0;
        rand = new Random(123);
    }

    /* Public Methods: */ /**
     * Creates n files with random amount of lines.
     * @param n The amount of files to be created.
     * @return String array with all the file paths.
     */
    public static String[] createFiles(int n) {
        String[] array = new String[n];
        for (int i=0; i<n; i++) {
            array[i] = String.format("C:FilesFile_%d.txt", i+1);
            try (   // Try with Resources: 
                    FileWriter fw = new FileWriter(array[i]); 
                    PrintWriter pw = new PrintWriter(fw);
                    ) {
                int numLines = getRand();
                for (int j=0; j<numLines; j++) pw.println(MSG);
            } catch (IOException ex) {
                System.err.println(String.format("Failed Writing to file: %s", 
                        array[i]));
            }
        }
        return array;
    }

    /**
     * Deletes all the files who's file paths are specified 
     * in the fileNames array.
     * @param fileNames The files to be deleted.
     */
    public static void deleteFiles(String[] fileNames) {
        for (String fileName : fileNames) {
            File file = new File(fileName);
            if (file.exists()) {
                file.delete();
            }
        }
    }

    /**
     * Creates numFiles amount of files.<br>
     * Counts how many lines are in all the files via Multi-threading.<br>
     * Deletes all the files when finished.
     * @param numFiles The amount of files to be created.
     */
    public static void countLinesThread(int numFiles) {
        Initialize();
        /* Create Files */
        String[] fileNames = createFiles(numFiles);
        Thread[] running = new Thread[numFiles];
        int k=0;
        long start = System.currentTimeMillis();
        /* Start all threads */
        for (String fileName : fileNames) {
            LineCounter thread = new LineCounter(fileName);
            running[k++] = thread;
            thread.start();
        }
        /* Join all threads */
        for (Thread thread : running) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                // Shouldn't happen.
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("threads time = %d ms, lines = %d",
                end-start,count));
        /* Delete all files */
        deleteFiles(fileNames);
    }

    @SuppressWarnings("CallToThreadRun")
    /**
     * Creates numFiles amount of files.<br>
     * Counts how many lines are in all the files in one process.<br>
     * Deletes all the files when finished.
     * @param numFiles The amount of files to be created. 
     */
    public static void countLinesOneProcess(int numFiles) {
        Initialize();
        /* Create Files */
        String[] fileNames = createFiles(numFiles);
        /* Iterate Files*/
        long start = System.currentTimeMillis();
        LineCounter thread;
        for (String fileName : fileNames) {
            thread = new LineCounter(fileName);
            thread.run(); // same process
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("linear time = %d ms, lines = %d",
                end-start,count));
        /* Delete all files */
        deleteFiles(fileNames);
    }

    public static void main(String[] args) {
        int num = 1000;
        countLinesThread(num);
        countLinesOneProcess(num);
    }

    /**
     * Auxiliary class designed to count the amount of lines in a text file.
     */// NESTED CLASS LINECOUNTER START:
    private static class LineCounter extends Thread {

        /* Privates: */
        private String fileName;

        /* Constructor: */
        private LineCounter(String fileName) {
            this.fileName=fileName;
        }

        /* Methods: */

        /**
         * Reads a file and counts the amount of lines it has.
         */ @Override
        public void run() {
            int count=0;
            try ( // Try with Resources:
                    FileReader fr = new FileReader(fileName);
                    //Scanner sc = new Scanner(fr);
                    BufferedReader br = new BufferedReader(fr);
                    ) {
                String str;
                for (str=br.readLine(); str!=null; str=br.readLine()) count++;
                //for (; sc.hasNext(); sc.nextLine()) count++;
                incrementCount(count);
            } catch (IOException e) {
                System.err.println(String.format("Failed Reading from file: %s", 
                fileName));            
            }
        }
    } // NESTED CLASS LINECOUNTER END;
} // CLASS MATALA_4A END;

4 ответов


могут быть различные факторы:

  • самое главное-избежать доступа к диску из нескольких потоков одновременно (но так как вы находитесь на SSD, вам это может сойти с рук). Однако на обычном жестком диске переключение из одного файла в другой может стоить вам 10 мс времени поиска (в зависимости от того, как данные кэшируются).

  • 1000 потоков слишком много, попробуйте использовать Количество ядер * 2. Слишком много времени будет потеряно на переключение контекстов только.

  • попробуйте использовать пул потоков. Общее время между 110ms и 130ms, часть этого будет от создания потоков.

  • сделайте еще несколько работ в тесте в целом. Время 110ms не всегда так точно. Также зависит от того, какие другие процессы или потоки выполняются в это время.

  • попытаться изменить порядок ваших тестов, чтобы увидеть, если это имеет значение (кэширование может быть важным фактор)

    countLinesThread(num);
    countLinesOneProcess(num);
    

кроме того, в зависимости от системы,currentTimeMillis() может иметь разрешение от 10 до 15 мс. Так что это не очень точное время коротких пробегов.

long start = System.currentTimeMillis();
long end = System.currentTimeMillis();

узким местом является диск.

вы можете получить доступ к диску только с одним потоком за раз, поэтому использование нескольких потоков не помогает, а вместо этого сверхурочное время, необходимое для переключения потоков, замедлит ваши глобальные характеристики.

использование многопоточности интересно, только если вам нужно разделить работу, ожидая длительных операций ввода-вывода на разных источниках (например, сеть и диск, или два разных диска, или много сетевых потоков) или если у вас есть процессор интенсивный операция, которая может быть разделена между различными ядрами.

помните, что для хорошей многопоточности программы вам всегда нужно учитывать:

  • переключение контекста между нитями
  • длинные операции ввода-вывода могут выполняться параллельно или нет
  • интенсивное время процессора для вычислений присутствует или нет
  • вычисления cpu могут быть разбиты на подзадачи или нет
  • сложность обмена данными между потоками (семафоры или синхронизация)
  • трудно читать, писать и управлять многопоточным кодом по сравнению с одним потоковым приложением

количество используемых потоков очень важно. один процесс, пытающийся переключаться между 1000 потоками (вы создали новый поток на файл), вероятно, является основной причиной медленнее.

попробуйте использовать, скажем, 10 потоков для чтения 1000 файлов, тогда вы увидите заметное увеличение скорости


Если фактическое время, необходимое для вычисления, незначительно по сравнению с временем, необходимым для ввода-вывода, потенциальные многопоточные преимущества также незначительны: один поток хорошо способен насытить ввод-вывод, а затем сделает очень быстрое вычисление; больше потоков не могут ускорить процесс. Вместо этого будут применяться обычные накладные расходы на поток, а также, возможно, штраф блокировки в реализации ввода-вывода, фактически уменьшающий пропускную способность.

Я думаю, что потенциал преимущества наибольшие, когда CPU время, необходимое для работы с куском данных долго по сравнению со временем, чтобы получить его с диска. В этом случае все потоки, кроме текущего считывания (если таковые имеются), могут вычисляться, и скорость выполнения должна хорошо масштабироваться с количеством ядер. Попробуйте проверить большое простое число кандидатов из файла или взломать зашифрованные строки (что, kindof, равносильно тому же, достаточно глупо).