mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
18 changed files with 462 additions and 283 deletions
@ -0,0 +1,52 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using SixLabors.ImageSharp.Memory.Internals; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Cached pointer or array data enabling fast <see cref="Span{T}"/> access from
|
||||
|
/// known <see cref="IMemoryOwner{T}"/> implementations.
|
||||
|
/// </summary>
|
||||
|
internal unsafe struct MemoryGroupSpanCache |
||||
|
{ |
||||
|
public SpanCacheMode Mode; |
||||
|
public byte[] SingleArray; |
||||
|
public void* SinglePointer; |
||||
|
public void*[] MultiPointer; |
||||
|
|
||||
|
public static MemoryGroupSpanCache Create<T>(IMemoryOwner<T>[] memoryOwners) |
||||
|
where T : struct |
||||
|
{ |
||||
|
IMemoryOwner<T> owner0 = memoryOwners[0]; |
||||
|
MemoryGroupSpanCache memoryGroupSpanCache = default; |
||||
|
if (memoryOwners.Length == 1) |
||||
|
{ |
||||
|
if (owner0 is SharedArrayPoolBuffer<T> sharedPoolBuffer) |
||||
|
{ |
||||
|
memoryGroupSpanCache.Mode = SpanCacheMode.SingleArray; |
||||
|
memoryGroupSpanCache.SingleArray = sharedPoolBuffer.Array; |
||||
|
} |
||||
|
else if (owner0 is UnmanagedBuffer<T> unmanagedBuffer) |
||||
|
{ |
||||
|
memoryGroupSpanCache.Mode = SpanCacheMode.SinglePointer; |
||||
|
memoryGroupSpanCache.SinglePointer = unmanagedBuffer.Pointer; |
||||
|
} |
||||
|
} |
||||
|
else if (owner0 is UnmanagedBuffer<T>) |
||||
|
{ |
||||
|
memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; |
||||
|
memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; |
||||
|
for (int i = 0; i < memoryOwners.Length; i++) |
||||
|
{ |
||||
|
memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer<T>)memoryOwners[i]).Pointer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return memoryGroupSpanCache; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Selects active values in <see cref="MemoryGroupSpanCache"/>.
|
||||
|
/// </summary>
|
||||
|
internal enum SpanCacheMode |
||||
|
{ |
||||
|
Default = default, |
||||
|
SingleArray, |
||||
|
SinglePointer, |
||||
|
MultiPointer |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.General |
||||
|
{ |
||||
|
public class Buffer2D_DangerousGetRowSpan |
||||
|
{ |
||||
|
private const int Height = 1024; |
||||
|
|
||||
|
[Params(0.5, 2.0, 10.0)] public double SizeMegaBytes { get; set; } |
||||
|
|
||||
|
private Buffer2D<Rgba32> buffer; |
||||
|
|
||||
|
[GlobalSetup] |
||||
|
public unsafe void Setup() |
||||
|
{ |
||||
|
int totalElements = (int)(1024 * 1024 * this.SizeMegaBytes) / sizeof(Rgba32); |
||||
|
|
||||
|
int width = totalElements / Height; |
||||
|
MemoryAllocator allocator = Configuration.Default.MemoryAllocator; |
||||
|
this.buffer = allocator.Allocate2D<Rgba32>(width, Height); |
||||
|
} |
||||
|
|
||||
|
[GlobalCleanup] |
||||
|
public void Cleanup() => this.buffer.Dispose(); |
||||
|
|
||||
|
[Benchmark] |
||||
|
public int DangerousGetRowSpan() => |
||||
|
this.buffer.DangerousGetRowSpan(1).Length + |
||||
|
this.buffer.DangerousGetRowSpan(Height - 1).Length; |
||||
|
|
||||
|
// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044
|
||||
|
// Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
|
||||
|
//
|
||||
|
// | Method | SizeMegaBytes | Mean | Error | StdDev |
|
||||
|
// |-------------------- |-------------- |----------:|----------:|----------:|
|
||||
|
// | DangerousGetRowSpan | 0.5 | 7.498 ns | 0.1784 ns | 0.3394 ns |
|
||||
|
// | DangerousGetRowSpan | 2 | 6.542 ns | 0.1565 ns | 0.3659 ns |
|
||||
|
// | DangerousGetRowSpan | 10 | 38.556 ns | 0.6604 ns | 0.8587 ns |
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Memory |
||||
|
{ |
||||
|
public partial class Buffer2DTests |
||||
|
{ |
||||
|
public class SwapOrCopyContent |
||||
|
{ |
||||
|
private readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void SwapOrCopyContent_WhenBothAllocated() |
||||
|
{ |
||||
|
using (Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean)) |
||||
|
using (Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean)) |
||||
|
{ |
||||
|
a[1, 3] = 666; |
||||
|
b[1, 3] = 444; |
||||
|
|
||||
|
Memory<int> aa = a.FastMemoryGroup.Single(); |
||||
|
Memory<int> bb = b.FastMemoryGroup.Single(); |
||||
|
|
||||
|
Buffer2D<int>.SwapOrCopyContent(a, b); |
||||
|
|
||||
|
Assert.Equal(bb, a.FastMemoryGroup.Single()); |
||||
|
Assert.Equal(aa, b.FastMemoryGroup.Single()); |
||||
|
|
||||
|
Assert.Equal(new Size(3, 7), a.Size()); |
||||
|
Assert.Equal(new Size(10, 5), b.Size()); |
||||
|
|
||||
|
Assert.Equal(666, b[1, 3]); |
||||
|
Assert.Equal(444, a[1, 3]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() |
||||
|
{ |
||||
|
using var destData = MemoryGroup<int>.Wrap(new int[100]); |
||||
|
using var dest = new Buffer2D<int>(destData, 10, 10); |
||||
|
|
||||
|
using (Buffer2D<int> source = this.MemoryAllocator.Allocate2D<int>(10, 10, AllocationOptions.Clean)) |
||||
|
{ |
||||
|
source[0, 0] = 1; |
||||
|
dest[0, 0] = 2; |
||||
|
|
||||
|
Buffer2D<int>.SwapOrCopyContent(dest, source); |
||||
|
} |
||||
|
|
||||
|
int actual1 = dest.DangerousGetRowSpan(0)[0]; |
||||
|
int actual2 = dest.DangerousGetRowSpan(0)[0]; |
||||
|
int actual3 = dest.GetSafeRowMemory(0).Span[0]; |
||||
|
int actual5 = dest[0, 0]; |
||||
|
|
||||
|
Assert.Equal(1, actual1); |
||||
|
Assert.Equal(1, actual2); |
||||
|
Assert.Equal(1, actual3); |
||||
|
Assert.Equal(1, actual5); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WhenBothAreMemoryOwners_ShouldSwap() |
||||
|
{ |
||||
|
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; |
||||
|
using Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(48, 2); |
||||
|
using Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(50, 2); |
||||
|
|
||||
|
Memory<int> a0 = a.FastMemoryGroup[0]; |
||||
|
Memory<int> a1 = a.FastMemoryGroup[1]; |
||||
|
Memory<int> b0 = b.FastMemoryGroup[0]; |
||||
|
Memory<int> b1 = b.FastMemoryGroup[1]; |
||||
|
|
||||
|
bool swap = Buffer2D<int>.SwapOrCopyContent(a, b); |
||||
|
Assert.True(swap); |
||||
|
|
||||
|
Assert.Equal(b0, a.FastMemoryGroup[0]); |
||||
|
Assert.Equal(b1, a.FastMemoryGroup[1]); |
||||
|
Assert.Equal(a0, b.FastMemoryGroup[0]); |
||||
|
Assert.Equal(a1, b.FastMemoryGroup[1]); |
||||
|
Assert.NotEqual(a.FastMemoryGroup[0], b.FastMemoryGroup[0]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WhenBothAreMemoryOwners_ShouldReplaceViews() |
||||
|
{ |
||||
|
using Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(100, 1); |
||||
|
using Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(100, 2); |
||||
|
|
||||
|
a.FastMemoryGroup[0].Span[42] = 1; |
||||
|
b.FastMemoryGroup[0].Span[33] = 2; |
||||
|
MemoryGroupView<int> aView0 = (MemoryGroupView<int>)a.MemoryGroup; |
||||
|
MemoryGroupView<int> bView0 = (MemoryGroupView<int>)b.MemoryGroup; |
||||
|
|
||||
|
Buffer2D<int>.SwapOrCopyContent(a, b); |
||||
|
Assert.False(aView0.IsValid); |
||||
|
Assert.False(bView0.IsValid); |
||||
|
Assert.ThrowsAny<InvalidOperationException>(() => _ = aView0[0].Span); |
||||
|
Assert.ThrowsAny<InvalidOperationException>(() => _ = bView0[0].Span); |
||||
|
|
||||
|
Assert.True(a.MemoryGroup.IsValid); |
||||
|
Assert.True(b.MemoryGroup.IsValid); |
||||
|
Assert.Equal(2, a.MemoryGroup[0].Span[33]); |
||||
|
Assert.Equal(1, b.MemoryGroup[0].Span[42]); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(false)] |
||||
|
[InlineData(true)] |
||||
|
public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) |
||||
|
{ |
||||
|
var data = new Rgba32[21]; |
||||
|
var color = new Rgba32(1, 2, 3, 4); |
||||
|
|
||||
|
using var destOwner = new TestMemoryManager<Rgba32>(data); |
||||
|
using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1); |
||||
|
|
||||
|
using Buffer2D<Rgba32> source = this.MemoryAllocator.Allocate2D<Rgba32>(21, 1); |
||||
|
|
||||
|
source.FastMemoryGroup[0].Span[10] = color; |
||||
|
|
||||
|
// Act:
|
||||
|
bool swap = Buffer2D<Rgba32>.SwapOrCopyContent(dest, source); |
||||
|
|
||||
|
// Assert:
|
||||
|
Assert.False(swap); |
||||
|
Assert.Equal(color, dest.MemoryGroup[0].Span[10]); |
||||
|
Assert.NotEqual(source.FastMemoryGroup[0], dest.FastMemoryGroup[0]); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(false)] |
||||
|
[InlineData(true)] |
||||
|
public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) |
||||
|
{ |
||||
|
var data = new Rgba32[21]; |
||||
|
var color = new Rgba32(1, 2, 3, 4); |
||||
|
|
||||
|
using var destOwner = new TestMemoryManager<Rgba32>(data); |
||||
|
using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1); |
||||
|
|
||||
|
using Buffer2D<Rgba32> source = this.MemoryAllocator.Allocate2D<Rgba32>(22, 1); |
||||
|
|
||||
|
source.FastMemoryGroup[0].Span[10] = color; |
||||
|
|
||||
|
// Act:
|
||||
|
Assert.ThrowsAny<InvalidOperationException>(() => Buffer2D<Rgba32>.SwapOrCopyContent(dest, source)); |
||||
|
|
||||
|
Assert.Equal(color, source.MemoryGroup[0].Span[10]); |
||||
|
Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,107 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers |
|
||||
{ |
|
||||
public partial class MemoryGroupTests |
|
||||
{ |
|
||||
public class SwapOrCopyContent : MemoryGroupTestsBase |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void WhenBothAreMemoryOwners_ShouldSwap() |
|
||||
{ |
|
||||
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; |
|
||||
using MemoryGroup<int> a = this.MemoryAllocator.AllocateGroup<int>(100, 50); |
|
||||
using MemoryGroup<int> b = this.MemoryAllocator.AllocateGroup<int>(120, 50); |
|
||||
|
|
||||
Memory<int> a0 = a[0]; |
|
||||
Memory<int> a1 = a[1]; |
|
||||
Memory<int> b0 = b[0]; |
|
||||
Memory<int> b1 = b[1]; |
|
||||
|
|
||||
bool swap = MemoryGroup<int>.SwapOrCopyContent(a, b); |
|
||||
|
|
||||
Assert.True(swap); |
|
||||
Assert.Equal(b0, a[0]); |
|
||||
Assert.Equal(b1, a[1]); |
|
||||
Assert.Equal(a0, b[0]); |
|
||||
Assert.Equal(a1, b[1]); |
|
||||
Assert.NotEqual(a[0], b[0]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void WhenBothAreMemoryOwners_ShouldReplaceViews() |
|
||||
{ |
|
||||
using MemoryGroup<int> a = this.MemoryAllocator.AllocateGroup<int>(100, 100); |
|
||||
using MemoryGroup<int> b = this.MemoryAllocator.AllocateGroup<int>(120, 100); |
|
||||
|
|
||||
a[0].Span[42] = 1; |
|
||||
b[0].Span[33] = 2; |
|
||||
MemoryGroupView<int> aView0 = a.View; |
|
||||
MemoryGroupView<int> bView0 = b.View; |
|
||||
|
|
||||
MemoryGroup<int>.SwapOrCopyContent(a, b); |
|
||||
Assert.False(aView0.IsValid); |
|
||||
Assert.False(bView0.IsValid); |
|
||||
Assert.ThrowsAny<InvalidOperationException>(() => _ = aView0[0].Span); |
|
||||
Assert.ThrowsAny<InvalidOperationException>(() => _ = bView0[0].Span); |
|
||||
|
|
||||
Assert.True(a.View.IsValid); |
|
||||
Assert.True(b.View.IsValid); |
|
||||
Assert.Equal(2, a.View[0].Span[33]); |
|
||||
Assert.Equal(1, b.View[0].Span[42]); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData(false)] |
|
||||
[InlineData(true)] |
|
||||
public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) |
|
||||
{ |
|
||||
var data = new Rgba32[21]; |
|
||||
var color = new Rgba32(1, 2, 3, 4); |
|
||||
|
|
||||
using var destOwner = new TestMemoryManager<Rgba32>(data); |
|
||||
using var dest = MemoryGroup<Rgba32>.Wrap(destOwner.Memory); |
|
||||
|
|
||||
using MemoryGroup<Rgba32> source = this.MemoryAllocator.AllocateGroup<Rgba32>(21, 30); |
|
||||
|
|
||||
source[0].Span[10] = color; |
|
||||
|
|
||||
// Act:
|
|
||||
bool swap = MemoryGroup<Rgba32>.SwapOrCopyContent(dest, source); |
|
||||
|
|
||||
// Assert:
|
|
||||
Assert.False(swap); |
|
||||
Assert.Equal(color, dest[0].Span[10]); |
|
||||
Assert.NotEqual(source[0], dest[0]); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData(false)] |
|
||||
[InlineData(true)] |
|
||||
public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) |
|
||||
{ |
|
||||
var data = new Rgba32[21]; |
|
||||
var color = new Rgba32(1, 2, 3, 4); |
|
||||
|
|
||||
using var destOwner = new TestMemoryManager<Rgba32>(data); |
|
||||
var dest = MemoryGroup<Rgba32>.Wrap(destOwner.Memory); |
|
||||
|
|
||||
using MemoryGroup<Rgba32> source = this.MemoryAllocator.AllocateGroup<Rgba32>(22, 30); |
|
||||
|
|
||||
source[0].Span[10] = color; |
|
||||
|
|
||||
// Act:
|
|
||||
Assert.ThrowsAny<InvalidOperationException>(() => MemoryGroup<Rgba32>.SwapOrCopyContent(dest, source)); |
|
||||
|
|
||||
Assert.Equal(color, source[0].Span[10]); |
|
||||
Assert.NotEqual(color, dest[0].Span[10]); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue