Как я могу наиболее эффективно использовать в своих интересах несколько ядер для коротких вычислений в.NET?

Вот контекст: Я пишу интерпретатор в C# для маленького языка программирования по имени Heron, и он начинает некоторые примитивные операции списка, которые могут быть выполнены параллельно.

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

Одна вещь, о которой я не должен волноваться, синхронизирует данные: параллельным операциям явно не позволяют изменить данные.

Таким образом, основные вопросы, которые я имею:

  • Что самый эффективный путь состоит в том, чтобы распределить работу через потоки, так, чтобы я мог гарантировать, что компьютер распределит работу через два ядра?

Я также интересуюсь связанным вопросом:

  • Примерно, сколько времени операция должна взять, прежде чем мы сможем начать преодолевать издержки разделения работы на другой поток?

8
задан cdiggins 8 February 2010 в 12:28
поделиться

3 ответа

Если вы хотите много работать с параллельными операциями, вам следует начать с .Net 4.0. Вот документация Параллельное программирование для .Net . Вы можете начать здесь, хотя . .Net 4.0 добавляет МНОГО с точки зрения использования многоядерных процессоров. Вот краткий пример:

Текущий 3.5 последовательный метод:

for(int i = 0; i < 30000; i++)
{
  doSomething(i);
}

Новый параллельный метод .Net 4.0:

Parallel.For(0, 30000, (i) => doSomething(i));

Метод Parallel.For автоматически масштабируется по количеству доступных ядер, вы можете увидеть, как быстро вы можете начать пользоваться этим.В структуре есть десятки новых библиотек, поддерживающих полное управление потоками / задачами, как в вашем примере (включая все конвейеры для синхронизации, отмены и т. Д.).

Существуют библиотеки для параллельного LINQ (PLINQ) , фабрик задач , планировщиков задач и некоторых других. Короче говоря, для конкретной задачи, которую вы изложили .Net 4.0 имеет для вас огромные преимущества, и я бы взял бесплатную бета-версию 2 ( RC скоро появится ) и получу начал. (Нет, я не работаю в Microsoft ... но я редко вижу, чтобы предстоящий выпуск так хорошо отвечал потребностям, поэтому я настоятельно рекомендую вам .Net 4.0)

15
ответ дан 5 December 2019 в 07:35
поделиться

Because I didn't want to develop using VS 2010, and I found that ThreadPool didn't have optimal performance for distributing work across cores (I think because it started/stopped too many threads) I ended up rolling my own. Hope that others find this useful:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace HeronEngine
{
    /// <summary>
    /// Represents a work item.
    /// </summary>
    public delegate void Task();

    /// <summary>
    /// This class is intended to efficiently distribute work 
    /// across the number of cores. 
    /// </summary>
    public static class Parallelizer 
    {
        /// <summary>
        /// List of tasks that haven't been yet acquired by a thread 
        /// </summary>
        static List<Task> allTasks = new List<Task>();

        /// <summary>
        /// List of threads. Should be one per core. 
        /// </summary>
        static List<Thread> threads = new List<Thread>();

        /// <summary>
        /// When set signals that there is more work to be done
        /// </summary>
        static ManualResetEvent signal = new ManualResetEvent(false);

        /// <summary>
        /// Used to tell threads to stop working.
        /// </summary>
        static bool shuttingDown = false;

        /// <summary>
        /// Creates a number of high-priority threads for performing 
        /// work. The hope is that the OS will assign each thread to 
        /// a separate core.
        /// </summary>
        /// <param name="cores"></param>
        public static void Initialize(int cores)
        {
            for (int i = 0; i < cores; ++i)
            {
                Thread t = new Thread(ThreadMain);
                // This system is not designed to play well with others
                t.Priority = ThreadPriority.Highest;
                threads.Add(t);
                t.Start();
            }
        }

        /// <summary>
        /// Indicates to all threads that there is work
        /// to be done.
        /// </summary>
        public static void ReleaseThreads()
        {
            signal.Set();
        }

        /// <summary>
        /// Used to indicate that there is no more work 
        /// to be done, by unsetting the signal. Note: 
        /// will not work if shutting down.
        /// </summary>
        public static void BlockThreads()
        {
            if (!shuttingDown)
                signal.Reset();
        }

        /// <summary>
        /// Returns any tasks queued up to perform, 
        /// or NULL if there is no work. It will reset
        /// the global signal effectively blocking all threads
        /// if there is no more work to be done.
        /// </summary>
        /// <returns></returns>
        public static Task GetTask()
        {
            lock (allTasks)
            {
                if (allTasks.Count == 0)
                {
                    BlockThreads();
                    return null;
                }
                Task t = allTasks.Peek();
                allTasks.Pop();
                return t;
            }
        }

        /// <summary>
        /// Primary function for each thread
        /// </summary>
        public static void ThreadMain()
        {
            while (!shuttingDown)
            {
                // Wait until work is available
                signal.WaitOne();

                // Get an available task
                Task task = GetTask();

                // Note a task might still be null becaue
                // another thread might have gotten to it first
                while (task != null)
                {
                    // Do the work
                    task();

                    // Get the next task
                    task = GetTask();
                }
            }
        }

        /// <summary>
        /// Distributes work across a number of threads equivalent to the number 
        /// of cores. All tasks will be run on the available cores. 
        /// </summary>
        /// <param name="localTasks"></param>
        public static void DistributeWork(List<Task> localTasks)
        {
            // Create a list of handles indicating what the main thread should wait for
            WaitHandle[] handles = new WaitHandle[localTasks.Count];

            lock (allTasks)
            {
                // Iterate over the list of localTasks, creating a new task that 
                // will signal when it is done.
                for (int i = 0; i < localTasks.Count; ++i)
                {
                    Task t = localTasks[i];

                    // Create an event used to signal that the task is complete
                    ManualResetEvent e = new ManualResetEvent(false);

                    // Create a new signaling task and add it to the list
                    Task signalingTask = () => { t(); e.Set(); };
                    allTasks.Add(signalingTask);

                    // Set the corresponding wait handler 
                    handles[i] = e;
                }
            }

            // Signal to waiting threads that there is work
            ReleaseThreads();

            // Wait until all of the designated work items are completed.
            Semaphore.WaitAll(handles);
        }

        /// <summary>
        /// Indicate to the system that the threads should terminate
        /// and unblock them.
        /// </summary>
        public static void CleanUp()
        {
            shuttingDown = true;
            ReleaseThreads();
        }
    }    
}
5
ответ дан 5 December 2019 в 07:35
поделиться

Я бы выбрал пул потоков, хотя у него есть свои проблемы, MS инвестирует в его улучшение, и похоже, что в .NET 4 он будет улучшен. На данный момент, я думаю, что лучше всего использовать пул потоков, обернутый в ваш собственный объект, и подождать с принятием решения о вашей собственной реализации

2
ответ дан 5 December 2019 в 07:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: