Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize MemoryMarshal.ToEnumerable for arrays and strings #89274

Merged
merged 1 commit into from Jul 21, 2023

Conversation

stephentoub
Copy link
Member

Method Toolchain Length Mean Ratio Allocated Alloc Ratio
CountArrayLINQ \main\corerun.exe 1 32.808 ns 1.00 64 B 1.00
CountArrayLINQ \pr\corerun.exe 1 9.272 ns 0.28 - 0.00
CountArray \main\corerun.exe 1 21.079 ns 1.00 64 B 1.00
CountArray \pr\corerun.exe 1 13.257 ns 0.63 32 B 0.50
CountArraySlice \main\corerun.exe 1 17.804 ns 1.00 64 B 1.00
CountArraySlice \pr\corerun.exe 1 5.121 ns 0.29 - 0.00
CountNonArray \main\corerun.exe 1 23.013 ns 1.00 64 B 1.00
CountNonArray \pr\corerun.exe 1 24.884 ns 1.08 64 B 1.00
CountArrayLINQ \main\corerun.exe 1000 3,129.629 ns 1.000 64 B 1.00
CountArrayLINQ \pr\corerun.exe 1000 9.484 ns 0.003 - 0.00
CountArray \main\corerun.exe 1000 3,627.104 ns 1.00 64 B 1.00
CountArray \pr\corerun.exe 1000 1,400.046 ns 0.41 32 B 0.50
CountArraySlice \main\corerun.exe 1000 3,579.278 ns 1.00 64 B 1.00
CountArraySlice \pr\corerun.exe 1000 1,920.087 ns 0.54 64 B 1.00
CountNonArray \main\corerun.exe 1000 5,087.494 ns 1.00 64 B 1.00
CountNonArray \pr\corerun.exe 1000 4,562.180 ns 0.90 64 B 1.00
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);

[MemoryDiagnoser(false)]
public class Tests
{
    private Memory<char> _array;
    private Memory<char> _arraySlice;
    private Memory<char> _nonArray;

    [Params(1, 1000)]
    public int Length { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _array = Enumerable.Repeat('a', Length).ToArray();
        _arraySlice = _array.Slice(1);
        _nonArray = new WrapperMemoryManager<char>(_array).Memory;
    }

    [Benchmark]
    public int CountArrayLINQ() => Enumerable.Count(MemoryMarshal.ToEnumerable<char>(_array));

    [Benchmark]
    public int CountArray() => Count(MemoryMarshal.ToEnumerable<char>(_array));

    [Benchmark]
    public int CountArraySlice() => Count(MemoryMarshal.ToEnumerable<char>(_arraySlice));

    [Benchmark]
    public int CountNonArray() => Count(MemoryMarshal.ToEnumerable<char>(_nonArray));

    private static int Count<T>(IEnumerable<T> source)
    {
        int count = 0;
        foreach (T item in source) count++;
        return count;
    }

    private sealed class WrapperMemoryManager<T>(Memory<T> memory) : MemoryManager<T>
    {
        public override Span<T> GetSpan() => memory.Span;
        public override MemoryHandle Pin(int elementIndex = 0) => throw new NotSupportedException();
        public override void Unpin() => throw new NotSupportedException();
        protected override void Dispose(bool disposing) { }
    }
}

@ghost
Copy link

ghost commented Jul 20, 2023

Tagging subscribers to this area: @dotnet/area-system-memory
See info in area-owners.md if you want to be subscribed.

Issue Details
Method Toolchain Length Mean Ratio Allocated Alloc Ratio
CountArrayLINQ \main\corerun.exe 1 32.808 ns 1.00 64 B 1.00
CountArrayLINQ \pr\corerun.exe 1 9.272 ns 0.28 - 0.00
CountArray \main\corerun.exe 1 21.079 ns 1.00 64 B 1.00
CountArray \pr\corerun.exe 1 13.257 ns 0.63 32 B 0.50
CountArraySlice \main\corerun.exe 1 17.804 ns 1.00 64 B 1.00
CountArraySlice \pr\corerun.exe 1 5.121 ns 0.29 - 0.00
CountNonArray \main\corerun.exe 1 23.013 ns 1.00 64 B 1.00
CountNonArray \pr\corerun.exe 1 24.884 ns 1.08 64 B 1.00
CountArrayLINQ \main\corerun.exe 1000 3,129.629 ns 1.000 64 B 1.00
CountArrayLINQ \pr\corerun.exe 1000 9.484 ns 0.003 - 0.00
CountArray \main\corerun.exe 1000 3,627.104 ns 1.00 64 B 1.00
CountArray \pr\corerun.exe 1000 1,400.046 ns 0.41 32 B 0.50
CountArraySlice \main\corerun.exe 1000 3,579.278 ns 1.00 64 B 1.00
CountArraySlice \pr\corerun.exe 1000 1,920.087 ns 0.54 64 B 1.00
CountNonArray \main\corerun.exe 1000 5,087.494 ns 1.00 64 B 1.00
CountNonArray \pr\corerun.exe 1000 4,562.180 ns 0.90 64 B 1.00
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);

[MemoryDiagnoser(false)]
public class Tests
{
    private Memory<char> _array;
    private Memory<char> _arraySlice;
    private Memory<char> _nonArray;

    [Params(1, 1000)]
    public int Length { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _array = Enumerable.Repeat('a', Length).ToArray();
        _arraySlice = _array.Slice(1);
        _nonArray = new WrapperMemoryManager<char>(_array).Memory;
    }

    [Benchmark]
    public int CountArrayLINQ() => Enumerable.Count(MemoryMarshal.ToEnumerable<char>(_array));

    [Benchmark]
    public int CountArray() => Count(MemoryMarshal.ToEnumerable<char>(_array));

    [Benchmark]
    public int CountArraySlice() => Count(MemoryMarshal.ToEnumerable<char>(_arraySlice));

    [Benchmark]
    public int CountNonArray() => Count(MemoryMarshal.ToEnumerable<char>(_nonArray));

    private static int Count<T>(IEnumerable<T> source)
    {
        int count = 0;
        foreach (T item in source) count++;
        return count;
    }

    private sealed class WrapperMemoryManager<T>(Memory<T> memory) : MemoryManager<T>
    {
        public override Span<T> GetSpan() => memory.Span;
        public override MemoryHandle Pin(int elementIndex = 0) => throw new NotSupportedException();
        public override void Unpin() => throw new NotSupportedException();
        protected override void Dispose(bool disposing) { }
    }
}
Author: stephentoub
Assignees: -
Labels:

area-System.Memory

Milestone: -

@stephentoub stephentoub merged commit 7982376 into dotnet:main Jul 21, 2023
168 checks passed
@stephentoub stephentoub deleted the toenumerable branch July 21, 2023 13:32
@adamsitnik adamsitnik added the tenet-performance Performance related issue label Jul 24, 2023
@dotnet dotnet locked as resolved and limited conversation to collaborators Aug 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants