Skip to content

Commit 53df3e9

Browse files
committed
обновление, до 21.09.2026. без новых unit тестов
1 parent 3db84a7 commit 53df3e9

11 files changed

Lines changed: 867 additions & 619 deletions

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,5 @@ ImageConvolution/Agent_Processed_Output/
489489
ImageConvolution/Processed_Output_Par/
490490
ImageConvolution/Processed_Output_Seq/
491491
ImageConvolution/Processed_Output_*/
492+
repomix-output.*
493+
repomix-output.xml

‎ImageConvolution/AgentProcessor.cs‎

Lines changed: 70 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,87 @@
1-
using System;
21
using System.Collections.Concurrent;
3-
using System.Collections.Generic;
42
using System.Diagnostics;
5-
using System.IO;
6-
using System.Threading.Tasks;
3+
using System.Runtime.ExceptionServices;
74

8-
using Microsoft.VisualBasic;
5+
namespace ImageConvolution;
96

10-
namespace ImageConvolution
7+
public class ImageToProcess
118
{
12-
public class ImageToProcess
13-
{
14-
public string FilePath { get; set; } = "";
15-
}
16-
17-
public class ImageResult
18-
{
19-
public string OriginalFileName { get; set; } = "";
20-
public double[,] ProcessedData { get; set; } = new double[0, 0];
21-
}
9+
public string FilePath { get; set; } = "";
10+
public double[,] ImageData { get; set; } = new double[0, 0];
11+
}
2212

13+
public class ImageResult
14+
{
15+
public string OriginalFileName { get; set; } = "";
16+
public double[,] ProcessedData { get; set; } = new double[0, 0];
17+
}
2318

24-
public class AgentProcessor
19+
public class AgentProcessor
20+
{
21+
public static void ProcessImagesWithAgents(string inputDir, string outputDir, int workerCount,
22+
int queueCapacity = 4, long memoryBudgetBytes = 0)
2523
{
26-
public static void ProcessImagesWithAgents(string inputDir, string outputDir, int workerCount)
24+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(workerCount);
25+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(queueCapacity);
26+
ArgumentOutOfRangeException.ThrowIfNegative(memoryBudgetBytes);
27+
if (!Directory.Exists(inputDir)) return;
28+
ImageFiles.ValidateDirectories(inputDir, outputDir);
29+
Directory.CreateDirectory(outputDir);
30+
var stopwatch = Stopwatch.StartNew();
31+
var files = ImageFiles.GetFiles(inputDir);
32+
using var slots = new SemaphoreSlim(ImageFiles.GetInFlightLimit(files, memoryBudgetBytes, 16));
33+
using var input = new BlockingCollection<ImageToProcess>(queueCapacity);
34+
using var output = new BlockingCollection<ImageResult>(queueCapacity);
35+
using var cancellation = new CancellationTokenSource();
36+
var token = cancellation.Token;
37+
Exception? failure = null;
38+
Task Start(Action action) => Task.Factory.StartNew(() =>
2739
{
28-
Console.WriteLine($"Запуск конвейера с {workerCount} агентами");
29-
if (!Directory.Exists(inputDir)) return;
30-
Directory.CreateDirectory(outputDir);
31-
32-
var filesToProcessQueue = new BlockingCollection<ImageToProcess>(100);
33-
var processedImagesQueue = new BlockingCollection<ImageResult>(100);
34-
35-
var stopwatch = Stopwatch.StartNew();
36-
var readerAgent = Task.Run(() =>
40+
try { action(); }
41+
catch (OperationCanceledException) when (token.IsCancellationRequested) { }
42+
catch (Exception ex)
3743
{
38-
39-
foreach (var file in Directory.GetFiles(inputDir, "*jpg"))
44+
Interlocked.CompareExchange(ref failure, ex, null);
45+
cancellation.Cancel();
46+
}
47+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
48+
var reader = Start(() =>
49+
{
50+
try
51+
{
52+
foreach (var file in files)
4053
{
41-
filesToProcessQueue.Add(new ImageToProcess { FilePath = file });
54+
slots.Wait(token);
55+
input.Add(new ImageToProcess { FilePath = file, ImageData = ImageIO.LoadAsGrayscale(file) }, token);
4256
}
43-
44-
filesToProcessQueue.CompleteAdding();
45-
});
46-
47-
var workerAgents = new List<Task>();
48-
for (int i = 0; i < workerCount; i++)
57+
}
58+
finally { input.CompleteAdding(); }
59+
});
60+
var workers = Enumerable.Range(0, workerCount).Select(_ => Start(() =>
61+
{
62+
foreach (var item in input.GetConsumingEnumerable(token))
4963
{
50-
var worker = Task.Run(() =>
64+
var result = new ImageResult
5165
{
52-
foreach (var imageToProcess in filesToProcessQueue.GetConsumingEnumerable())
53-
{
54-
double[,] image = ImageIO.LoadAsGrayscale(imageToProcess.FilePath);
55-
double[,] processedImage = ConvolutionProcessor.Convolve(image, Kernels.BlurBox);
56-
var ResultForQue = new ImageResult
57-
{
58-
OriginalFileName = Path.GetFileName(imageToProcess.FilePath),
59-
ProcessedData = processedImage
60-
};
61-
processedImagesQueue.Add(ResultForQue);
62-
}
63-
});
64-
workerAgents.Add(worker);
66+
OriginalFileName = Path.GetFileName(item.FilePath),
67+
ProcessedData = ConvolutionProcessor.Convolve(item.ImageData, Kernels.BlurBox)
68+
};
69+
item.ImageData = new double[0, 0];
70+
output.Add(result, token);
6571
}
66-
67-
var allWorkersTask = Task.WhenAll(workerAgents).ContinueWith(t =>
68-
{
69-
processedImagesQueue.CompleteAdding();
70-
});
71-
72-
73-
var writerAgent = Task.Run(() =>
72+
})).ToArray();
73+
var completion = Task.WhenAll(workers).ContinueWith(_ => output.CompleteAdding(), TaskScheduler.Default);
74+
var writer = Start(() =>
75+
{
76+
foreach (var result in output.GetConsumingEnumerable(token))
7477
{
75-
76-
foreach (var result in processedImagesQueue.GetConsumingEnumerable())
77-
{
78-
string savePath = Path.Combine(outputDir, result.OriginalFileName);
79-
ImageIO.SaveImage(result.ProcessedData, savePath);
80-
}
81-
});
82-
83-
Task.WaitAll(readerAgent, allWorkersTask, writerAgent);
84-
85-
stopwatch.Stop();
86-
Console.WriteLine($"\nОбработка конвейером завершена за {stopwatch.ElapsedMilliseconds} мс.");
87-
}
78+
ImageIO.SaveImage(result.ProcessedData, Path.Combine(outputDir, result.OriginalFileName));
79+
result.ProcessedData = new double[0, 0];
80+
slots.Release();
81+
}
82+
});
83+
Task.WaitAll(reader, completion, writer);
84+
if (failure != null) ExceptionDispatchInfo.Capture(failure).Throw();
85+
Console.WriteLine($"Обработка конвейером ({workerCount} агентов): {files.Length} файлов за {stopwatch.Elapsed.TotalMilliseconds:F1} мс.");
8886
}
89-
}
87+
}

‎ImageConvolution/BatchProcessor.cs‎

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,94 @@
11
using System;
22
using System.Diagnostics;
33
using System.IO;
4-
using System.Net;
5-
using System.Runtime.Intrinsics.Arm;
64
using System.Threading.Tasks;
75

86
namespace ImageConvolution
97
{
10-
public class BatchProcessor
8+
public enum ParallelStrategy
119
{
10+
Sequential,
11+
ParallelByRows,
12+
ParallelByColumns
13+
}
1214

13-
public static void ProcessImagesNaiveParallel(string inputDirectory, string outputDirectory, bool useParallelConvolutionInside)
15+
public class BatchProcessor
16+
{
17+
public static void ProcessImagesNaiveParallel(string inputDirectory, string outputDirectory, ParallelStrategy strategy,
18+
int maxDegreeOfParallelism = 0, long memoryBudgetBytes = 0)
1419
{
15-
if (!Directory.Exists(inputDirectory))
16-
{
17-
Console.WriteLine("Ошибка: Указанная папка не существует.");
18-
return;
19-
}
20+
ArgumentOutOfRangeException.ThrowIfNegative(maxDegreeOfParallelism);
21+
ArgumentOutOfRangeException.ThrowIfNegative(memoryBudgetBytes);
22+
if (!Enum.IsDefined(strategy)) throw new ArgumentOutOfRangeException(nameof(strategy));
23+
if (!Directory.Exists(inputDirectory)) return;
24+
ImageFiles.ValidateDirectories(inputDirectory, outputDirectory);
25+
Directory.CreateDirectory(outputDirectory);
26+
string[] files = ImageFiles.GetFiles(inputDirectory);
2027

21-
if (!Directory.Exists(outputDirectory))
22-
{
23-
Directory.CreateDirectory(outputDirectory);
24-
}
28+
var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism == 0 ? Environment.ProcessorCount : maxDegreeOfParallelism };
2529

26-
string[] files = Directory.GetFiles(inputDirectory, "*.jpg");
30+
int gen0Start = GC.CollectionCount(0);
31+
int gen1Start = GC.CollectionCount(1);
32+
int gen2Start = GC.CollectionCount(2);
2733

28-
Console.WriteLine($"Найдено файлов: {files.Length}");
34+
using var process = Process.GetCurrentProcess();
35+
TimeSpan cpuTimeStart = process.TotalProcessorTime;
36+
var sw = Stopwatch.StartNew();
37+
options.MaxDegreeOfParallelism = Math.Min(options.MaxDegreeOfParallelism, ImageFiles.GetInFlightLimit(files, memoryBudgetBytes, 16));
38+
Console.WriteLine($"Параллельных файлов: {options.MaxDegreeOfParallelism}; свёртка: {strategy}");
2939

30-
31-
Parallel.ForEach(files, currentFile =>
40+
Parallel.ForEach(files, options, currentFile =>
3241
{
3342
string fileName = Path.GetFileName(currentFile);
34-
3543
string savePath = Path.Combine(outputDirectory, fileName);
3644

3745
double[,] imageData = ImageIO.LoadAsGrayscale(currentFile);
38-
3946
double[,] result;
40-
if (useParallelConvolutionInside)
41-
{
42-
result = ParallelConvolutionProcessor.ConvolveParallel(imageData, Kernels.BlurBox);
43-
}
44-
else
47+
48+
switch (strategy)
4549
{
46-
result = ConvolutionProcessor.Convolve(imageData, Kernels.BlurBox);
50+
case ParallelStrategy.Sequential:
51+
result = ConvolutionProcessor.Convolve(imageData, Kernels.BlurBox);
52+
break;
53+
54+
case ParallelStrategy.ParallelByRows:
55+
result = ParallelConvolutionProcessor.ConvolveParallel(imageData, Kernels.BlurBox);
56+
break;
57+
58+
case ParallelStrategy.ParallelByColumns:
59+
result = ParallelConvolutionProcessor.ConvolveParallelByColumns(imageData, Kernels.BlurBox);
60+
break;
61+
62+
default:
63+
throw new ArgumentException("Unknown strategy");
4764
}
4865

4966
ImageIO.SaveImage(result, savePath);
5067
});
68+
69+
sw.Stop();
70+
TimeSpan cpuTimeEnd = process.TotalProcessorTime;
71+
72+
int gc0 = GC.CollectionCount(0) - gen0Start;
73+
int gc1 = GC.CollectionCount(1) - gen1Start;
74+
int gc2 = GC.CollectionCount(2) - gen2Start;
75+
76+
double realTimeMs = sw.Elapsed.TotalMilliseconds;
77+
double cpuTimeMs = (cpuTimeEnd - cpuTimeStart).TotalMilliseconds;
78+
79+
string strategyName = strategy switch
80+
{
81+
ParallelStrategy.Sequential => "Последовательная",
82+
ParallelStrategy.ParallelByRows => "Параллельная по строкам (Y)",
83+
ParallelStrategy.ParallelByColumns => "Параллельная по столбцам (X)",
84+
_ => "Неизвестная"
85+
};
86+
87+
Console.WriteLine($"\n--- РЕЗУЛЬТАТЫ ПРОФИЛИРОВАНИЯ ({strategyName}) ---");
88+
Console.WriteLine($"Реальное время: {realTimeMs:F1} мс");
89+
Console.WriteLine($"Процессорное время (сумма по всем ядрам): {cpuTimeMs:F1} мс");
90+
Console.WriteLine($"Сборки мусора (GC): Gen0={gc0}, Gen1={gc1}, Gen2={gc2}");
91+
Console.WriteLine("----------------------------------------------------------\n");
5192
}
5293
}
53-
}
94+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using BenchmarkDotNet.Attributes;
3+
4+
namespace ImageConvolution
5+
{
6+
[MemoryDiagnoser]
7+
public class ConvolutionBenchmarks
8+
{
9+
private double[,] imageDouble = null!;
10+
private float[,] imageFloat = null!;
11+
private GpuConvolutionProcessor gpuProcessor = null!;
12+
13+
[Params(512, 1024)]
14+
public int ImageSize { get; set; }
15+
16+
[GlobalSetup]
17+
public void Setup()
18+
{
19+
imageDouble = new double[ImageSize, ImageSize];
20+
imageFloat = new float[ImageSize, ImageSize];
21+
22+
var rand = new Random(42);
23+
for (int y = 0; y < ImageSize; y++)
24+
{
25+
for (int x = 0; x < ImageSize; x++)
26+
{
27+
double val = rand.Next(0, 256);
28+
imageDouble[y, x] = val;
29+
imageFloat[y, x] = (float)val;
30+
}
31+
}
32+
33+
gpuProcessor = new GpuConvolutionProcessor();
34+
35+
_ = gpuProcessor.ConvolveGpu(imageFloat, Kernels.BlurBoxFloat, EdgeStrategy.Extend);
36+
}
37+
38+
[GlobalCleanup]
39+
public void Cleanup()
40+
{
41+
gpuProcessor?.Dispose();
42+
}
43+
44+
[Benchmark(Baseline = true, Description = "1. CPU (Один поток)")]
45+
public double[,] CpuSequential()
46+
{
47+
return ConvolutionProcessor.Convolve(imageDouble, Kernels.BlurBox, EdgeStrategy.Extend);
48+
}
49+
50+
[Benchmark(Description = "2. CPU (Parallel.For)")]
51+
public double[,] CpuParallel()
52+
{
53+
return ParallelConvolutionProcessor.ConvolveParallel(imageDouble, Kernels.BlurBox, EdgeStrategy.Extend);
54+
}
55+
56+
[Benchmark(Description = "CPU (Parallel.For по столбцам)")]
57+
public double[,] CpuParallelColumns() => ParallelConvolutionProcessor.ConvolveParallelByColumns(imageDouble, Kernels.BlurBox);
58+
59+
[Benchmark(Description = "CPU (float, один поток)")]
60+
public float[,] CpuFloat() => UnifiedProcessor.ConvolveCpu(imageFloat, Kernels.BlurBoxFloat, EdgeStrategy.Extend);
61+
62+
[Benchmark(Description = "ImageSharp BoxBlur 3x3 + преобразования")]
63+
public float[,] ImageSharpBox() => LibraryProcessor.ConvolveBox(imageFloat);
64+
65+
[Benchmark(Description = "3. GPU (PCIe + Math)")]
66+
public float[,] GpuFullCycle()
67+
{
68+
return gpuProcessor.ConvolveGpu(imageFloat, Kernels.BlurBoxFloat, EdgeStrategy.Extend);
69+
}
70+
71+
[Benchmark(Description = "4. GPU (запуск + ядро + синхронизация)")]
72+
public void GpuMathOnly()
73+
{
74+
gpuProcessor.ConvolveGpuMathOnly_ForBenchmark(Kernels.BlurBoxFloat, EdgeStrategy.Extend);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)