Оптимизация производительности C#.NET (Алгоритм, Многопоточность, Debug, Release, .Net Core, Net Native)

Узнай цену своей работы

Формулировка задачи:

Решил поделится своим небольшим опытом по оптимизации вычислений на C#.NET. НЕ профи, палками не кидать, конструктив приветствуется! Тестом будет служить время вычисление всех переменных в заданном диапазоне (до 100000) в уравнении x^3 + y^3 = z^3 - 1 Симметричные решения по x и y не учитываем, т.е. из вариантов х=6,у=8,z=9 и х=8,у=6,z=9 - берем один (любой). Оборудование/Софт:
Тип ЦП QuadCore AMD Phenom II X4 Black Edition 955, 3200 MHz (16 x 200) Системная плата Asus M4A89GTD Pro (2 PCI, 1 PCI-E x1, 1 PCI-E x4, 2 PCI-E x16, 4 DDR3 DIMM, Audio, Video, Gigabit LAN, IEEE-1394) Чипсет системной платы AMD 890GX, AMD K10 Системная память 8192 МБ (DDR3-1333 DDR3 SDRAM) Софт Win10x64, Excel2016x64, Visual Studio 2017
ШАГ1. Все кажется просто, берем и перебором ищем значения. Здесь сражу же упираемся в большое количество итераций - время выполнение неприлично большое. Поэтому ШАГ2 - Оптимизируем алгоритм, создаем массив из степеней Z, переберем значения х, у, обрезаем ненужные итерации, оставляем только один вариант x,y и обрезаем итерации на суммах x^3 + y^3 > z^3. Здесь (код) пальма первенства справедливо принадлежит

m-ch

- опять неприлично долго в VBA более 10 мин. ШАГ3. Берем этот алгоритм, переносим на C# и делим потоки, благо это задача хорошо бьется по потокам. Код (Сборка Debug x64):
Листинг программы
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. //поиск переменных для решения уравнения x^3 + y^3 = z^3 - 1
  5. class Program
  6. {
  7. static long n; static long k = 0;
  8. static long[] a;
  9. static void Main(string[] args)
  10. {
  11. var threads = new List<Thread>();
  12. for (int i = 2; i <= 9; i++)
  13. {
  14. int k = i;
  15. threads.Add(new Thread(() => func(k)));
  16. }
  17. Console.Write("Задайте диапазон расчета переменных, до..., нажмите Enter: ");
  18. n = Convert.ToInt64(Console.ReadLine());
  19. a = new long[n + 1];
  20. DateTime dold = DateTime.Now;
  21. for (long i = 1; i <= n; i++)
  22. {
  23. a[i] = (long)Math.Pow(i, 3);
  24. }
  25. threads.ForEach(t => t.Start());
  26. threads.ForEach(t => t.Join()); //ждем выполнения всех потоков
  27. TimeSpan sp = DateTime.Now - dold;
  28. Console.WriteLine(sp);
  29. Console.Write("Программа завершена. Нажмите Enter для выхода...");
  30. Console.ReadLine();
  31. }
  32. static void func(long start)
  33. {
  34. // здесь код, который будет выполняться в отдельном потоке
  35. for (long x = start; x <= (long)Math.Pow(((a[n]) - 1) / 2, 1.0 / 3.0); x = x + 8)//на 8 потоков
  36. {
  37. for (long y = x; y <= (long)Math.Pow(((a[n]) - 1 - a[x]), 1.0 / 3.0); y++)
  38. {
  39. long z3 = a[x] + a[y] + 1;
  40. long z = (long)(Math.Pow(z3, 1.0 / 3.0) + 0.5);
  41. if (a[z] == z3)
  42. {
  43. k = ++k;
  44. Console.WriteLine(k + " " + x + " " + y + " " + z);
  45. }
  46. }
  47. }
  48. }
  49. }
Время выполнения чуть более 4 минут (по ссылке выше есть так же расчет и на Freebasic). Пока прохладно. ШАГ4. В C# (как проверено и не только в C#) расчет корня 3 степени медленнее, чем прямой перебор в массиве с некоторой хитростью (т.к. он отсортирован и перебор всегда ведется от меньшего к большему, или наоборот, легко запоминать последнюю найденную позицию и искать начиная с неё в след. цикле). Эта мысль меня посетила и была сразу реализована. Код (Сборка Debug x64) - делю на 4 потока, т.к. больше для моего ЦП -нет смысла:
Листинг программы
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Diagnostics;
  5. namespace ConsoleApplication1
  6. {
  7. //поиск переменных для решения уравнения x^3 + y^3 = z^3 - 1
  8. class Program
  9. {
  10. static long n; static long k = 0; static long[] a;
  11. static long threadsN = 4; //задать 4 потоков
  12. static void Main(string[] args)
  13. {
  14. var threads = new List<Thread>();
  15. for (int i = 1; i <= threadsN; i++)
  16. {
  17. int k = i;
  18. threads.Add(new Thread(() => func(k)));
  19. }
  20. Console.Write("Задайте диапазон расчета переменных, до..., нажмите Enter: ");
  21. n = Convert.ToInt64(Console.ReadLine());
  22. a = new long[n + 1];
  23. //DateTime dold = DateTime.Now;
  24. Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
  25. for (long i = 1; i <= n; i++) a[i] = (i * i * i); // Math.Pow не использовал из-за ошибок округления
  26. threads.ForEach(t => t.Start());//стартуем потоки
  27. threads.ForEach(t => t.Join()); //ждем выполнения всех потоков
  28. sw.Stop();
  29. //Console.WriteLine(sp);
  30. Console.WriteLine("Время вычисления: {0}", sw.Elapsed);
  31. Console.Write("Программа завершена. Нажмите Enter для выхода...");
  32. Console.ReadLine();
  33. }
  34. static void func(long start)
  35. {
  36. // здесь код, который будет выполняться в отдельном потоке
  37. long m = n;
  38. for (long x = start; x <= (long)Math.Pow(((a[n]) - 1) / 2, 1.0 / 3.0); x = x + threadsN)//на 4 потоков
  39. {
  40. long s = 1;
  41. while (a[m] > (a[n]) - 1 - a[x]) m--;
  42. for (long y = x; y <= m; y++)
  43. {
  44. long z3 = a[x] + a[y] + 1;
  45. while ((a[s]) <= z3)
  46. {
  47. if (a[s] == z3)
  48. {
  49. k++;
  50. Console.WriteLine(k + " " + x + " " + y + " " + s);
  51. }
  52. s++;
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }
Время выполнения - 41 сек. Уже теплее. ШАГ5. Как же я забыл про Release, срочно исправляюсь - Сборка Release x64. За счет оптимизации при компилировании получаем время выполнения - 17 секунд. Уже тепло. Появляются мысли про оптимизацию цикла (в сети множество советов и примеров), т.к. для вычисления требуется несколько миллиардов итераций. Думаю. ШАГ6. Почитав про .Net Core, решил попробовать. Новый проект - Консольное приложение .Net Core - вставляем предыдущий код и ... Время выполнения - 10,5 сек. Здесь мне кажется, что сделан максимум на C#. ШАГ7. Решил добить тему и погуглить. Есть интересный зверь net native, подробно писать здесь не буду. Кратко: В Windows 10 универсальные Windows-приложения на управляемых языках (C#, VB) проходят в магазине процедуру компиляции в машинный код с использованием .NET Native. Поэтому: создать проект-пустое приложение (универсальное приложение Windows)(UWP). Создаем button, textBox (на кнопку код, в textBox - переменные). Здесь нас поджидает проблемы с Threading. Переходим на Task, двигаем дальше. Код (Сборка Release x64)
Листинг программы
  1. using System;
  2. using System.Collections.Generic;
  3. using Windows.UI.Xaml;
  4. using Windows.UI.Xaml.Controls;
  5. using System.Threading.Tasks;
  6. using System.Diagnostics;
  7. namespace App1
  8. {
  9. public sealed partial class MainPage : Page
  10. {
  11. static long n; static long k = 0; static long[] a; static string str = "";
  12. static long TaskN = 4; //задать 4 task
  13. public MainPage()
  14. {
  15. this.InitializeComponent();
  16. }
  17. private void Button_Click(object sender, RoutedEventArgs e)
  18. {
  19. n = 100000;
  20. a = new long[n + 1];
  21. Stopwatch sw = Stopwatch.StartNew();
  22. for (long i = 1; i <= n; i++) a[i] = (i * i * i); // Math.Pow не использовал из-за ошибок округления
  23. var task = new List<Task>();
  24. for (int i = 1; i <= TaskN; i++)
  25. {
  26. int k = i;
  27. task.Add(new Task(() => func(k)));
  28. }
  29. task.ForEach(t => t.Start());//стартуем task
  30. task.ForEach(t => t.Wait()); //ждем выполнения всех task
  31. sw.Stop();
  32. button.Content = sw.Elapsed.TotalSeconds;
  33. textBox.Text = str;
  34. }
  35. static void func(long start)
  36. {
  37. // здесь код, который будет выполняться в отдельном task
  38. long m = n;
  39. for (long x = start; x <= (long)Math.Pow(((a[n]) - 1) / 2, 1.0 / 3.0); x=x+TaskN) //на 4 task
  40. {
  41. long s = 1;
  42. while (a[m] > (a[n]) - 1 - a[x]) m--;
  43. for (long y = x; y <= m; y++)
  44. {
  45. long z3 = a[x] + a[y] + 1;
  46. while ((a[s]) <= z3)
  47. {
  48. if (a[s] == z3)
  49. {
  50. k++;
  51. str = str + Environment.NewLine + (k + " " + x + " " + y + " " + s);
  52. }
  53. s++;
  54. }
  55. }
  56. }
  57. }
  58. }
  59. }
Время выполнения 7,5 сек. Без вывода строки 5 сек.(подумать со строкой) ШАГ8. АССЕМБЛЕР... здесь мои знания заканчиваются... Что скажут ОТЦЫ, есть ли дальнейшая оптимизация? В блоге.

Решение задачи: «Оптимизация производительности C#.NET (Алгоритм, Многопоточность, Debug, Release, .Net Core, Net Native)»

textual
Листинг программы
  1. z = Int(x * 1.2599)

ИИ поможет Вам:


  • решить любую задачу по программированию
  • объяснить код
  • расставить комментарии в коде
  • и т.д
Попробуйте бесплатно

Оцени полезность:

7   голосов , оценка 4 из 5

Нужна аналогичная работа?

Оформи быстрый заказ и узнай стоимость

Бесплатно
Оформите заказ и авторы начнут откликаться уже через 10 минут
Похожие ответы