Код внутри потока медленнее, чем внешний поток ..? - PullRequest
2 голосов
/ 15 ноября 2010

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

Для пояснения: оригинальный код, назовем его

//doSomething

, получил Runnable вокруг неговот так:

Runnable r = new Runnable()
{
    public void run()
    {
        //doSomething
    }
}

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

Однако это не так.Когда // doSomething выполняется примерно за 2 секунды, Runnable выполняется примерно за 2,5 секунды. Я должен отметить, что некоторый другой код, скажем, // doSomethingElse внутри Runnable не имел потери производительности по сравнению с исходным // doSomethingElse ,

Я предполагаю, что // doSomething имеет некоторые операции, которые не так быстры при работе в потоке, но я не знаю, что это может быть или что, вэтот аспект отличается от // doSomethingElse .

Может ли это быть использование final int [] / float [] массивов, что делает Runnable намного медленнее?Код // doSomethingElse также использовал некоторые финалы, но // doSomething использует больше.Это единственное, о чем я мог подумать.

К сожалению, код // doSomething довольно длинный и вне контекста, но я все равно опубликую его здесь.Для тех, кто знает алгоритм сегментации среднего сдвига, это часть кода, где вычисляется вектор среднего сдвига для каждого пикселя.Цикл for

for(int i=0; i<L; i++) 

проходит через каждый пиксель.

timer.start(); // this is where I start the timer
// Initialize mode table used for basin of attraction
char[] modeTable = new char [L]; // (L is a class property and is about 100,000)
Arrays.fill(modeTable, (char)0);
int[] pointList = new int [L];

// Allcocate memory for yk (current vector)
double[] yk = new double [lN]; // (lN is a final int, defined earlier)

// Allocate memory for Mh (mean shift vector)
double[] Mh = new double [lN];

int idxs2 = 0; int idxd2 = 0;
for (int i = 0; i < L; i++) {
    // if a mode was already assigned to this data point
    // then skip this point, otherwise proceed to
    // find its mode by applying mean shift...
    if (modeTable[i] == 1) {
        continue;
    }

    // initialize point list...
    int pointCount = 0;

    // Assign window center (window centers are
    // initialized by createLattice to be the point
    // data[i])
    idxs2 = i*lN;
    for (int j=0; j<lN; j++)
    yk[j] = sdata[idxs2+j]; // (sdata is an earlier defined final float[] of about 100,000 items)

    // Calculate the mean shift vector using the lattice
    /*****************************************************/
    // Initialize mean shift vector
    for (int j = 0; j < lN; j++) {
        Mh[j] = 0;
    }
    double wsuml = 0;
    double weight;

    // find bucket of yk
    int cBucket1 = (int) yk[0] + 1;
    int cBucket2 = (int) yk[1] + 1;
    int cBucket3 = (int) (yk[2] - sMinsFinal) + 1;
    int cBucket = cBucket1 + nBuck1*(cBucket2 + nBuck2*cBucket3);
    for (int j=0; j<27; j++) {
        idxd2 = buckets[cBucket+bucNeigh[j]]; // (buckets is a final int[] of about 75,000 items)
        // list parse, crt point is cHeadList
        while (idxd2>=0) {
            idxs2 = lN*idxd2;
            // determine if inside search window
            double el = sdata[idxs2+0]-yk[0];
            double diff = el*el;
            el = sdata[idxs2+1]-yk[1];
            diff += el*el;
            //...
            idxd2 = slist[idxd2]; // (slist is a final int[] of about 100,000 items)
        }
    }
    //...
}
timer.end(); // this is where I stop the timer.

Код больше, но последний цикл while был тем местом, где я впервые заметил разницув производительности.

Может кто-нибудь придумать причину, почему этот код работает внутри Runnable медленнее, чем оригинал?

Спасибо.

Редактировать: Измеренное время равно внутри кода, поэтому исключая запуск потока.

Ответы [ 4 ]

3 голосов
/ 15 ноября 2010

Весь код всегда выполняется "внутри потока".

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

Единого правильного способа разделения задач не существует, однако все зависит от того, как выглядят данные и как выглядит целевая машина (2 ядра, 8 ядер, 512 ядер?).

Редактировать: Что произойдет, если вы повторно запустите тест? Например, если вы сделаете это так:

Executor executor = ...;
for (int i = 0; i < 10; i++) {
    final int lap = i;
    Runnable r = new Runnable() {
        public void run() {
            long start = System.currentTimeMillis();
            //doSomething
            long duration = System.currentTimeMillis() - start;
            System.out.printf("Lap %d: %d ms%n", lap, duration);   
        }
    };
    executor.execute(r);
}

Заметили ли вы разницу в результатах?

1 голос
/ 15 ноября 2010

Лично я не вижу в этом причин.Любая программа имеет хотя бы один поток.Все темы равны.Все потоки создаются по умолчанию со средним приоритетом (5).Таким образом, код должен показывать одинаковую производительность как в основном потоке приложения, так и в другом потоке, который вы открываете.

Вы уверены, что измеряете время «что-то сделать», а не общее время выполнения вашей программы?Я считаю, что вы измеряете время операции вместе со временем, необходимым для создания и запуска потока.

0 голосов
/ 15 ноября 2010

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

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

Здесь различие между делами "Нет нити" и "Резьба" на самом деле состоит в том, что у вас уже нет одной нити (как было указано, у вас всегда есть нить) и два потока, поэтому теперь JVM должна быть посредником между двумя потоками. Для такой работы я не понимаю, почему это должно иметь значение, но это разница.

Я бы хотел использовать хороший инструмент для профилирования, чтобы действительно в этом разобраться.

0 голосов
/ 15 ноября 2010

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

Просто деталь:Решение о том, насколько большой может быть задача, которую можно распараллелить, все еще стоит, является известной темой параллельных вычислений:)

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