Страница: 1 | 2 | 3 | 4 | 5 |
|
Вопрос: Два ядра под VB6
|
Добавлено: 08.08.11 21:43
|
|
Номер ответа: 47 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #47
|
Добавлено: 14.08.11 04:23
|
Объясняю на примере. C# 4.0, ибо другого не признаю.
Есть функция, которая производит некоторые вычисления. Функция полностью изолированная, принимает на вход какой-то аргумент, что-то считает, возварщает результат. Не модифицирует никаких общих ресурсов.
- private static double Calculate(double x)
- {
- double result = x;
- for (int i = 0; i < 100000; i++)
- {
- result += 1;
- }
- return result;
- }
Функция немного читерская, так как вся нагрузка уйдет на процессор, что позволит получить хорошую масштабируемость на нескольких ядрах.
Нам нужно ее посчитать для 100000 разных X. Пишем код который сгенерирует для теста значения от 0 до 100000
Пишем функцию, которая принимает 100000 аргументов, возвращает столько же результатов. Делает это в цикле, без многопоточности (кстати, многопоточность можно прикручивать только когда у тебя есть 100% рабочий однопоточный вариант. Это же касается любой оптимизации которую собираешься делать).
- private static double[] Calculate(double[] xArguments)
- {
- double[] result = new double[xArguments.Length];
- for (int i = 0; i < result.Length; i++)
- {
- result = Calculate(xArguments);
- }
- return result;
- }
Теперь код который сгенерирует данные и все это запустит.
- static void Main(string[] args)
- {
- double[] arguments = getXArguments();
-
- Stopwatch stopwatch = Stopwatch.StartNew();
- double[] result = Calculate(arguments);
- Console.WriteLine(stopwatch.Elapsed);
- Console.ReadLine();
- }
-
- private static double[] getXArguments()
- {
- double[] result = new double[100000];
- for (int i = 0; i < result.Length; i++)
- {
- result = i;
- }
- return result;
- }
Результат выполнения - 11 секунд.
Теперь прикручиваем многопоточность.
Очевидно, что функция Calculate изолирована, поэтому мы можем делать одновременно несколько вычисления для разных аргументов.
Кол-во потоков, которые мы запускаем, будет равно кол-ву ядер. Во-первых, это позволит максимально нагрузить все ядра процессора, во-вторых, избавит Windows
Прежде всего, нужно распределить входящие данные между потоками. Есть разные способы это сделать. Например, если кол-во данных четко неизвестно, время обработки каждого элемента не постоянно, данные поступают во время работы, можно использовать producer-consumer. Но это не наш случай, мы знаем сколько данных, среднее время обработки каждого элемента будет примерно постоянным, поэтому сделаем проще. Разделим входной набор данных на несколько равных сегментов, по одному для каждого потока.
Ответить
|
Номер ответа: 48 Автор ответа: Ким Чен Ир
Вопросов: 0 Ответов: 140
|
Профиль | | #48
|
Добавлено: 14.08.11 04:29
|
Вопрос не ко мне, т.к. в моих словах и был скепсис при реализации такого подхода.
Кстати, при синхронизации процессов в юзермоде можно использовать interlocked-функции.
Через общую область памяти, file-mapping.
Ответить
|
Номер ответа: 49 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #49
|
Добавлено: 14.08.11 05:46
|
Рука соскочила, вобщем вот что я хотел сказать полностью.
Объясняю на примере. C# 4.0, ибо другого не признаю.
Есть функция, которая производит некоторые вычисления. Функция полностью изолированная, принимает на вход какой-то аргумент, что-то считает, возварщает результат. Не модифицирует никаких общих ресурсов.
- private static double Calculate(double x)
- {
- double result = x;
- for (int i = 0; i < 100000; i++)
- {
- result += 1;
- }
- return result;
- }
Функция немного читерская, так как вся нагрузка уйдет на процессор, что позволит получить хорошую масштабируемость на нескольких ядрах.
Нам нужно ее посчитать для 100000 разных X. Пишем код который сгенерирует для теста значения от 0 до 100000
Пишем функцию, которая принимает 100000 аргументов, возвращает столько же результатов. Делает это в цикле, без многопоточности (кстати, многопоточность можно прикручивать только когда у тебя есть 100% рабочий однопоточный вариант. Это же касается любой оптимизации которую собираешься делать).
- private static double[] Calculate(double[] xArguments)
- {
- double[] result = new double[xArguments.Length];
- for (int i = 0; i < result.Length; i++)
- {
- result[ i ] = Calculate(xArguments[ i ]);
- }
- return result;
- }
Теперь код который сгенерирует данные и все это запустит.
- static void Main(string[] args)
- {
- double[] arguments = getXArguments();
-
- Stopwatch stopwatch = Stopwatch.StartNew();
- double[] result = Calculate(arguments);
- Console.WriteLine(stopwatch.Elapsed);
- Console.ReadLine();
- }
-
- private static double[] getXArguments()
- {
- double[] result = new double[100000];
- for (int i = 0; i < result.Length; i++)
- {
- result[ i ] = i;
- }
- return result;
- }
Результат выполнения - 11 секунд.
Теперь прикручиваем многопоточность.
Очевидно, что функция Calculate изолирована, поэтому мы можем делать одновременно несколько вычисления для разных аргументов.
Кол-во потоков, которые мы запускаем, будет равно кол-ву ядер. Во-первых, это позволит максимально нагрузить все ядра процессора, во-вторых, избавит Windows от необходимости часто переключать контексты.
Прежде всего, нужно распределить входящие данные между потоками. Есть разные способы это сделать. Например, если кол-во данных четко неизвестно, время обработки каждого элемента не постоянно, данные поступают во время работы, можно использовать producer-consumer. Но это не наш случай, мы знаем сколько данных, среднее время обработки каждого элемента будет примерно постоянным, поэтому сделаем проще. Разделим входной набор данных на несколько равных сегментов, по одному для каждого потока.
-
- int segmentSize = xArguments.Length / threadsCount;
- for (int t = 0; t < threadsCount; t++)
- {
- int segmentStart = segmentSize * t;
- int segmentEnd = segmentStart + segmentSize;
- if (t == threadsCount - 1)
- {
- segmentEnd = result.Length - 1;
- }
-
- // Начало сегмента - SegmentStart. Конец сегмента - SegmentEnd
- }
Проверка для последнего сегмента стоит для того, чтоб в случае когда кол-во элементов не делится поровну на кол-во потоков не осталось в конце пропущенных элементов, которые не попали ни в один сегмент.
Пишем функцию, которая будет делать рассчет результатов для сегмента. В отличие от других, она не будет возвращать результат с массивом. Она принимает в качестве агрумента этот массив и записывает в него результаты вычислений. Делаем так, чтоб после работы не пришлось собирать в кучу несколько массивов.
-
- private static void CalculageSegment(double[] arguments, double[] result, int segmentStart, int segmentEnd)
- {
- for (int i = segmentStart; i <= segmentEnd; i++)
- {
- result[ i ] = Calculate(arguments[ i ]);
- }
- }
Теперь остается добавить код запуска потоков.
-
- private static double[] CalculateWithMT(double[] xArguments)
- {
- double[] result = new double[xArguments.Length];
-
- int threadsCount = System.Environment.ProcessorCount;
-
- int segmentSize = xArguments.Length / threadsCount;
- for (int t = 0; t < threadsCount; t++)
- {
- int segmentStart = segmentSize * t;
- int segmentEnd = segmentStart + segmentSize;
- if (t == threadsCount - 1)
- {
- segmentEnd = xArguments.Length - 1;
- }
-
- System.Threading.Thread thread = new Thread(()=>
- {
- CalculateSegment(xArguments, result, segmentStart, segmentEnd);
- });
- thread.Start();
- }
-
- return result;
- }
Как видим, основной поток не выполняет никаких вычислений, он только запускает несколько рабочих потоков, которые и выполнят основную работу.
Здесь есть большая проблема - основной поток вернет результат сразу после того как запустит потоки. В этот момент результатов еще никаких не будет. Поэтому нужно сначала дождаться пока потоки отработают, только после этого делать return. Для этого используем синхронизацию на основе массива ManualResetEvent
-
- private static double[] CalculateWithMT(double[] xArguments)
- {
- double[] result = new double[xArguments.Length];
-
- int threadsCount = System.Environment.ProcessorCount;
-
- ManualResetEvent[] waitHandles = new ManualResetEvent[threadsCount];
-
- int segmentSize = xArguments.Length / threadsCount;
- for (int t = 0; t < threadsCount; t++)
- {
- int segmentStart = segmentSize * t;
- int segmentEnd = segmentStart + segmentSize;
- if (t == threadsCount - 1)
- {
- segmentEnd = xArguments.Length - 1;
- }
-
- waitHandles[t] = new ManualResetEvent(false);
- ManualResetEvent manualResetEvent = waitHandles[t];
-
- Thread thread = new Thread(()=>
- {
- CalculateSegment(xArguments, result, segmentStart, segmentEnd, manualResetEvent);
- });
- thread.Start();
- }
-
- WaitHandle.WaitAll(waitHandles);
- return result;
- }
-
- private static void CalculateSegment(double[] arguments, double[] result, int segmentStart, int segmentEnd, ManualResetEvent waitHandle)
- {
- for (int i = segmentStart; i <= segmentEnd; i++)
- {
- result[ i ] = Calculate(arguments[ i ]);
- }
-
- waitHandle.Set();
- }
Результат выполнения - 6.3 секунды. Т.е. в 1.8 раза быстрее (на двухъядерном компьютере).
Если запустить на 4-ядерном компьютере (с поддержков HT, т.е. 8 логических ядер, далее называю его 8-ядерным), получим 11.16 секунд и 1.52 секунды - в 7.3 раза быстрее.
Вот то же самое на TPL
-
- public static double[] CalculateWithTPL(double[] xArguments)
- {
- double[] result = new double[xArguments.Length];
-
- Parallel.For(0, xArguments.Length, (i) => result[ i ] = Calculate(xArguments[ i ]));
-
- return result;
- }
Результат работы - 6.6 секунд на двухъядерном, 1.53 на 8-ядерном. Видны небольшие накладные расходы у TPL.
Вариант на PLinQ (это тоже TPL, но немного другой подход)
-
- public static double[] CalculateWithPLinq(double[] xArguments)
- {
- return xArguments.AsParallel().AsOrdered().Select(d => Calculate(d)).ToArray();
- }
7 секунд на двухъядерном, 2 секунды на 8-ядерном
========================================================================
Как видишь, никаких приоритетов и масок здесь не нужно. Нужно, тем не менее, понимать что происходит
Например, мой первый вариант повис после 3-го запуска. Оказалось, причина была в этом
-
- waitHandles[t] = new ManualResetEvent(false);
-
- Thread thread = new Thread(()=>
- {
- CalculateSegment(xArguments, result, segmentStart, segmentEnd, waitHandles[t]);
- });
Чтоб легко передать в поток параметры я использовал lambda выражение. И получил типичную гонку потоков. В тот момент, когда lambda выражение выполнялось переменная t успевала увеличиться на 1. И в оба потока попадал один и тот же manualResetEvent. Потоки его сбрасывали, но первый ManualResetEvent оставался заблокированным и функция продолжала висеть. С таким же успехом я мог получить что-то вроде ArgumnetOutOfrRangeException, если бы это происходило не при запуске первого потока, а при запуске второго. В конце цикла t увеличивается на 1, становится равной 2 и цикл завершается. После этого lambda выражение пытается считать из массива event'ов элемент по индексу 2, которого, раузмеется не существует (всего 2 элемента, индексация с 0).
TPL дает небольшое проседание скорости, но избавляет от необходимости решать многие задачи.
Моя задача очень хорошо распараллелилась. Во-первых потому что я почти не работаю с памятью. Во-вторых, потому что результаты я считываю и записываю и разных сегментов массива. Очевидно, что каждый сегмент попал в кеш того ядра, которое его обрабатывает и не было необходимости тратить время на синхронизацию доступа к общему участку памяти.
Можно написать программу, которая, как кажется, не модифицирует общую память, тем не менее, в реальности окажется, что она модифицирует участки памяти, лежащие рядом, и полностью попадающие в кеш того или иного ядра, остальным прийдется работать намного менее эфективно, поскольку модификация данных, которые лежат в кеше другого ядра займет много времени.
Ответить
|
Номер ответа: 53 Автор ответа: Сергей Юдин
Вопросов: 8 Ответов: 81
|
Профиль | | #53
|
Добавлено: 14.08.11 13:49
|
Объясняю на примере. C# 4.0, ибо другого не признаю.
А я считаю это не объяснением, а натуральным очковтирательством, т.к. Ваш пример не объясняет преимуществ многопоточности, а показывает, что 4-е землекопа выроют траншею в 4-е раза быстрее, чем один. С таким же успехом Ваш пример можно реализовать на 4-х разных компьютерах по 25000 слагаемых, а потом результаты просуммировать столбиком или на одном и том же компьютере 4-раза по 25000. Причем код бы в этом случае был самый примитивный и даже с затратой времени на сложение столбиком общие затраты времени в этом случае были бы на несколько порядков меньше, чем в Вашем варианте. Мне кажется, что, создавая многоядерные компьютеры, их разработчики хотели хотя бы частично устранить основной недостаток цифровых компьютеров, а именно последовательность вычислений. Ведь аналоговые компьютеры (сейчас остались известны только нейрокомпьютеры) производят параллельные во времени вычисления всех процессов протекающих в рассматриваемом явление природы или социальном феномене. И разработчики, быстрее всего, заложили в них эту возможность (вот только мы не сообразим, как ее реализовать).
С наилучшими пожеланиями Сергей Юдин.
Ответить
|
Номер ответа: 54 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #54
|
Добавлено: 14.08.11 14:18
|
А зачем ты пришел на форум спрашивать про многопоточность если ты считаешь что уже и так знаешь как там все работает?
Причем, судя по тому что ты пишешь ты вообще не имеешь ни малейшего представления об этом.
Не хочу тебя расстраивать, но суть именно в том что 4 землекопа выроют траншею в 4 раза быстрее. Причем в идеальном случае. Если им прийдется при этом постоянно согласовывать свои действия, ждать пока один человек на тележке вывозит землю, и у них будет 2 лопаты на 4-х, то в 4 раза быстрее они траншею не выкопают.
Сергей Юдин пишет:
С таким же успехом Ваш пример можно реализовать на 4-х разных компьютерах по 25000 слагаемых, а потом результаты просуммировать столбиком или на одном и том же компьютере 4-раза по 25000.
Это следующий этап. При этом существенно возрастают затраты на коммуникацию между процессами. Поэтому при масштабировании на несколько компьютеров нужно приложить больше усилий, чтоб минимизировать необходимость процесов взаимодействовать друг с другом. Собственно по такому принципу работает map-reduce. В гугле, например. Только вместо 4 компьютеров используется несколько тысяч.
Сергей Юдин пишет:
Причем код бы в этом случае был самый примитивный и даже с затратой времени на сложение столбиком общие затраты времени в этом случае были бы на несколько порядков меньше, чем в Вашем варианте.
Что за ерунду ты говоришь? Я просто написал функцию, которая грузит процессор и работает некоторое время. Понятное дело что можно посчитать все формулой прогрессии и т.п. Я не буду тратить время на то чтоб тебе сделать пример с матаном. Функцию сам пиши, какую тебе надо.
Сергей Юдин пишет:
Мне кажется, что, создавая многоядерные компьютеры, их разработчики хотели хотя бы частично устранить основной недостаток цифровых компьютеров, а именно последовательность вычислений. Ведь аналоговые компьютеры (сейчас остались известны только нейрокомпьютеры) производят параллельные во времени вычисления всех процессов протекающих в рассматриваемом явление природы или социальном феномене. И разработчики, быстрее всего, заложили в них эту возможность (вот только мы не сообразим, как ее реализовать).
Когда кажется - креститься нужно. Я тебе описываю не то что кажется, и не то что хотели, а то как это есть на самом деле.
Ответить
|
Номер ответа: 57 Автор ответа: Сергей Юдин
Вопросов: 8 Ответов: 81
|
Профиль | | #57
|
Добавлено: 15.08.11 06:18
|
А зачем ты пришел на форум спрашивать про многопоточность если ты считаешь что уже и так знаешь как там все работает?
Причем, судя по тому что ты пишешь ты вообще не имеешь ни малейшего представления об этом.
Я пришел, чтобы оптимизировать решение своей задачи, а не потрепаться на общие темы и показать какой я умный или выслушивать какой я глупый и рассматривать пример, который никакого отношения не имеет к моей задаче. Кстати, второй поток (правда, простейший) я запускал еще лет 6 назад и в конкретной своей задаче, а не в абстрактном примере. А вот ты Юпитер сердишься, значит, ты не прав (даже, если окажется, что ты был прав по поводу чисто экстенсивного пути развития современных цифровых компьютеров).
Без пожеланий Сергей Юдин.
Ответить
|
Номер ответа: 59 Автор ответа: Сергей Юдин
Вопросов: 8 Ответов: 81
|
Профиль | | #59
|
Добавлено: 15.08.11 22:36
|
Ты ждешь что здесь кто-то тебе будет писать пример под твою конкретную задачу? Чтоб ты просто скопипастил код в свой проект? Тебе тогда нужно было отправляться на freelance.ru
Мои знания получены из заслуживающих доверия источников (Рихтер, Руссинович, Тауб) и на основе личного опыта и пока сомнению не подлежат, по крайней мере кроме тебя еще никто не начал кричать что в интернете кто-то не прав
Слушай знахарь, хватит ТЫкать, т.к. мои познания по вопросам мироздания получены мною лично, а не из книжек. А программированием (по необходимости) я начал заниматься более 30 лет тому назад и работал на ЭВМ, когда у них еще не было ни дисплея, ни клавиатуры, т.е. насмотрелся на своем веку всяких великих ученых и в том числе по программированию. И если я усомнился в твоих познаниях, то это еще не значит, что ТЫ не прав. Возможно, действительно, создатели цифровых компьютеров уперлись в стену дальнейшего их прогресса, и пошли по чисто экстенсивному пути их развития, который не позволяет решать нам 99% реальных задач. На квантовый компьютер можешь не рассчитывать, т.к. мои познания квантовой механики позволяют заявить, что он не будет создан никогда. Аналоговые ЭВМ и нейрокомпьютеры хороши тем, что решают всю задачу за один проход множеством потоков, но в аналоговых сложно подобрать схему и промасштабировать его элементы, а в нейрокомпьютере трудно понять, почему получился такой результат. По этому, я и ожидал от многоядерных компьютеров прогресса именно в параллельности решения 99% задач, которые стоят перед нами, а не повышения мощности //однопоточного// компьютера при разбиение однопоточной задачи на части, не зависящие друг от друга, за счет увеличения ядер, т.к. тактовую частоту повышать дальше нельзя.
А по поводу первого вопроса, то вообще то я не надеялся, что кто-то займется конкретно моей задачей (хотя раньше таких прецедентов было много и я не очень удивлялся этому), но сейчас думаю, что это было бы не плохо, т.к. человек, занявшийся этим и, если бы у него это получилось, внес бы большой вклад в развитие науки, т.к. все наши научные задачи решаются именно так, как моя задача. А сейчас (последние три года) я занимаюсь поиском скорости распространения гравитации. Если все получится, то это будет окончательно означать, что Эйнштейн мошенник, т.к. пока это доказывается только опосредованно. Я считаю, что решаемая задача стоит и времени и сил, т.к. результат стоит того. Так что поумерьте свою гордыню, а если у ВАС это получится, то я не держу на ТЕБЯ обиду и ради большого дела готов согласиться на помощь в решение этой задачи.
По прежнему без пожеланий Сергей Юдин.
Ответить
|
Номер ответа: 60 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #60
|
Добавлено: 15.08.11 23:07
|
Если в интернете кто-то не прав, я это говорю. Вне зависимости от того сколько этот кто-то занимается программированием.
То что вас интерисует работает так как я описал. Спросите у любого другого специалиста, он повторит вам то же самое. И обвинять меня в чем-то только потому что вам хотелось чтоб все работало по другому очень глупо, не кажется?
Сергей Юдин пишет:
А по поводу первого вопроса, то вообще то я не надеялся, что кто-то займется конкретно моей задачей (хотя раньше таких прецедентов было много и я не очень удивлялся этому), но сейчас думаю, что это было бы не плохо, т.к. человек, занявшийся этим и, если бы у него это получилось, внес бы большой вклад в развитие науки, т.к. все наши научные задачи решаются именно так, как моя задача. А сейчас (последние три года) я занимаюсь поиском скорости распространения гравитации. Если все получится, то это будет окончательно означать, что Эйнштейн мошенник, т.к. пока это доказывается только опосредованно. Я считаю, что решаемая задача стоит и времени и сил, т.к. результат стоит того. Так что поумерьте свою гордыню, а если у ВАС это получится, то я не держу на ТЕБЯ обиду и ради большого дела готов согласиться на помощь в решение этой задачи.
Ну так в чем проблема тогда? Наймите специалиста который разбирается в матанализе и параллельном программировании, и двигайте науку.
Ответить
|
Страница: 1 | 2 | 3 | 4 | 5 |
Поиск по форуму