Как я могу распараллелить этот алгоритм C #? - PullRequest
0 голосов
/ 22 марта 2019

Я новичок в C #, поэтому я ничего не знаю о Task или потоках. Я написал этот код и хочу использовать параллельную или потоковую обработку.

Код состоит из двух таблиц DataTable (A & B), и я должен сравнить каждое значение ячейки A со всеми ячейками B. B состоит из одного столбца и нескольких строк. А может быть миллионы клеток. Я делаю это используя петли. это часть кода, которую я хотел бы распараллелить для ускорения процесса:

  private DataTable CalculosPrincipales(DataTable Prof, DataTable Prop, DataTable Rango)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Prof Evaluar", typeof(double));
            dt.Columns.Add("Profundidad", typeof(double));
            dt.Columns.Add("Promedio", typeof(double));
            dt.Columns.Add("Sumatoria", typeof(double));
            dt.Columns.Add("n", typeof(double));

            if (int.TryParse(box_Z.Text, out int z))
            {

            }
            var step = (progressBar.Properties.Maximum - (Int32)progressBar.EditValue)/z;

            for (int i = 0; i < Rango.Rows.Count-1; i++)
            {
                dt.Rows.Add(Rango.Rows[i][0], Rango.Rows[i][1], 0, 0 , 0);
            }

            double prof_celda;
            double prof_rango;
            double prop_celda;

            for (int i = 0; i < Prop.Rows.Count; i++)
            {
                for (int j = 0; j < Prop.Columns.Count; j++)
                {
                    prop_celda = Convert.ToDouble(Prop.Rows[i][j]);

                    if (prop_celda != nullvalue)
                    {
                        for (int k = 0; k < Rango.Rows.Count; k++)
                        {
                            prof_celda = Convert.ToDouble(Prof.Rows[i][j]);
                            prof_rango = Convert.ToDouble(Rango.Rows[k][0]);

                            if (prof_celda < prof_rango)
                            {
                                dt.Rows[k][3] = Convert.ToDouble(dt.Rows[k][3]) + prop_celda;
                                dt.Rows[k][4] =  Convert.ToInt32(dt.Rows[k][4]) + 1;
                                break;
                            }
                        }
                    }
                }
                progressBar.PerformStep();
                Application.DoEvents();
            }

            for (int i = 0; i < dt.Rows.Count; i++)
            {
                if (Convert.ToInt32(dt.Rows[i][4]) == 0)
                {
                    dt.Rows[i].Delete();
                    i -= 1;
                }
            }

            return dt;
        }

Этот код выполняется быстро, если в таблице A 10000 ячеек, для 200000 ячеек требуется 5 минут, а для 1000000 - 20 минут.

1 Ответ

0 голосов
/ 11 апреля 2019

Это пример распараллеливания вашего алгоритма. Однако использование DataTable вводит некоторые ограничения производительности. Вы должны рассмотреть возможность использования более подходящих классов.

Я внес следующие изменения:

  • Извлечен расчет в отдельный класс.
  • Разделить расчет на n задач.
  • Добавлена ​​поддержка отмены через CancellationTokenSource
  • Заменены активные отчеты о прогрессе на пассивные.
  • Добавлена ​​обработка исключений

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

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

Обратите внимание, что это не идеальная реализация, это просто пример, и он не тестировался.

Мне кажется, что ваше описание не совсем соответствует коду (вы говорили о 2 входных таблицах, но код работает с 3 - разве не одинаковые Prop и Prof?)

using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;

public class ParallelCalculation
{
    public delegate void CompletionHandler(DataTable result, Exception exception);
    public DataTable Prof, Prop, Rango;

    class Part
    {
        public DataTable Result;
        public int FromRow, ToRow;
        public float Progress;
        public Exception Exception;
    }

    DataTable result;
    Part[] parts;
    Task[] tasks;
    CancellationToken cancellation;
    CompletionHandler callback;

    public async void Run(CompletionHandler callback, CancellationToken token, int threadCount = 0)
    {
        this.cancellation = token;
        this.callback = callback;

        await Task.Factory.StartNew(Perform, threadCount);
    }

    async void Perform(object state)
    {
        int threadCount = (int)state;

        try
        {
            // Create table for results
            result = new DataTable();
            result.Columns.Add("Prof Evaluar", typeof(double));
            result.Columns.Add("Profundidad", typeof(double));
            result.Columns.Add("Promedio", typeof(double));
            result.Columns.Add("Sumatoria", typeof(double));
            result.Columns.Add("n", typeof(double));

            for (int i = 0; i < Rango.Rows.Count; i++)
                result.Rows.Add(Rango.Rows[i][0], Rango.Rows[i][1], 0, 0, 0);

            // Split calculation into n tasks. Tasks work in parallel,
            // each one processes it's own stripe of data, defined by the instance of the Part class.
            int n = threadCount > 0 ? threadCount : Environment.ProcessorCount;
            tasks = new Task[n];
            parts = new Part[n];
            int rowsPerTask = Prof.Rows.Count / n;
            int rest = Prof.Rows.Count % n;
            for (int i = 0, from = 0, to = 0; i < n; ++i, --rest, from = to)
            {
                to = from + rowsPerTask + (rest > 0 ? 1 : 0);
                parts[i] = new Part { FromRow = from, ToRow = to };
                tasks[i] =  Task.Factory.StartNew(CalculatePart, parts[i]);
            }

            // Wait until all partial calculations are finished
            await Task.WhenAll(tasks);

            // Sum partial results to the main result table (and find the first exception, if any)
            Exception e = null;
            foreach (var part in parts)
            {
                e = e ?? part.Exception;
                for (int row = 0; row < result.Rows.Count; ++row)
                {
                    result.Rows[row][3] = Convert.ToDouble(result.Rows[row][3]) + Convert.ToDouble(part.Result.Rows[row][3]);
                    result.Rows[row][4] = Convert.ToInt32(result.Rows[row][4]) + Convert.ToInt32(part.Result.Rows[row][4]);
                }
            }

            // Remove empty rows from results
            for (int i = 0; i < result.Rows.Count; i++)
            {
                if (Convert.ToInt32(result.Rows[i][4]) == 0)
                {
                    result.Rows[i].Delete();
                    i -= 1;
                }
            }

            // Call back 
            callback?.Invoke(result, e);
        }
        catch (Exception e)
        {
            callback?.Invoke(null, e);
        }
    }

    void CalculatePart(object state)
    {
        var part = (Part)state;
        try
        {
            // Create our own table for partial results.
            part.Result = this.result.Copy();

            var result = part.Result; // Just a shortcut

            int cols = Prop.Columns.Count;
            int steps = cols * (part.ToRow - part.FromRow);

            for (int i = part.FromRow, step = 1; i < part.ToRow; i++)
            {
                for (int j = 0; j < cols; j++, step++)
                {
                    var prop_celda_obj = Prop.Rows[i][j];
                    if (prop_celda_obj != DBNull.Value)
                    {
                        double prop_celda = Convert.ToDouble(prop_celda_obj);
                        double prof_celda = Convert.ToDouble(Prof.Rows[i][j]);

                        for (int k = 0; k < Rango.Rows.Count; k++)
                        {
                            //double prof_celda = Convert.ToDouble(Prof.Rows[i][j]);
                            double prof_rango = Convert.ToDouble(Rango.Rows[k][0]);

                            if (prof_celda < prof_rango)
                            {
                                result.Rows[k][3] = Convert.ToDouble(result.Rows[k][3]) + prop_celda;
                                result.Rows[k][4] = Convert.ToDouble(result.Rows[k][4]) + 1;
                                break;
                            }
                        }
                    }

                    part.Progress = step / (float)steps;
                    if (cancellation.IsCancellationRequested)
                        return;
                }
            }
        }
        catch (Exception e)
        {
            part.Exception = e;
        }
    }

    public float Progress()
    {
        float sum = 0.0f;
        foreach (var part in parts)
            sum += part.Progress;
        return sum / parts.Length;
    }
}

Следующий код является примером использования вышеуказанного класса в форме. Возможно, вам придется немного его адаптировать.

partial class MyForm {   

    Button btnStartStop;
    ProgressBar progressBar;

    // Do this somewhere:
    // btnStartStop.Click += BtnStartStop_Click;

    int threads = 0;              // 0 means "The number of CPU cores"
    DataTable Prof, Prop, Rango;  // You have to provide these values

    // The final results will be stored here:
    DataTable Result;

    CancellationTokenSource cancellation;
    ParallelCalculation calculation;
    System.Windows.Forms.Timer progressTimer;

    void BtnStartStop_Click(object sender, EventArgs e)
    {
        if (calculation != null)
            cancellation.Cancel();
        else
            StartCalculation();
    }

    void StartCalculation()
    {
        cancellation = new CancellationTokenSource();
        calculation = new ParallelCalculation { Prof = this.Prof, Prop = this.Prop, Rango = this.Rango };
        calculation.Run(Finished, cancellation.Token, threads);

        progressBar.Value = 0;
        progressTimer = new System.Windows.Forms.Timer(components) { Interval = 100 };
        progressTimer.Tick += ProgressTimer_Tick;
        progressTimer.Start();

        UpdateUI();
    }

    void Finished(DataTable table, Exception e)
    {
        BeginInvoke((Action)delegate
        {
            Result = table;
            progressBar.Value = (int)(calculation.Progress() * 100);
            progressTimer.Stop();
            progressTimer.Tick -= ProgressTimer_Tick;
            calculation = null;

            UpdateUI();
        });
    }

    private void ProgressTimer_Tick(object sender, EventArgs e)
    {
        if (calculation != null)
            progressBar.Value = (int)(calculation.Progress() * 100);
    }

    void UpdateUI()
    {
        btnStartStop.Text = calculation == null ? "Start" : "Stop";
    }
}
...