Skip to content

Conversation

loribao
Copy link

@loribao loribao commented Sep 6, 2025

Summary

  • Upgrade target framework from net8.0 to net9.0
  • Update GirCore packages to version 0.6.3 (Adw, Gtk-4.0, WebKit-6.0)
  • Update Microsoft packages to version 9.0.8 (Components.WebView, Extensions.Logging.Console, Extensions.Hosting)
  • Fix MemoryInputStream creation using GLib.Bytes.New instead of deprecated NewFromData method
  • Remove custom InputStream workaround class, no longer needed with updated GirCore
  • Add missing GLib.Internal using statement
  • Improve null safety with explicit nullable array parameters

Test plan

  • Verify application builds successfully with .NET 9
  • Test WebView functionality with updated WebKit packages
  • Confirm all dependencies resolve correctly

- Upgrade target framework from net8.0 to net9.0│
- Update GirCore packages to version 0.6.3 (Adw, Gtk-4.0, WebKit-6.0)│
- Update Microsoft packages to version 9.0.8 (Components.WebView, Extensions.Logging.Console, Extensions.Hosting)│
- Fix MemoryInputStream creation using GLib.Bytes.New instead of deprecated NewFromData method│
- Remove custom InputStream workaround class, no longer needed with updated GirCore
- Add missing GLib.Internal using statement│
- Improve null safety with explicit nullable array parameters
var streamPtr = MemoryInputStream.NewFromData(ref ms.GetBuffer()[0], (uint)ms.Length, _ => { });
var inputStream = new InputStream(streamPtr, false);
request.Finish(inputStream, ms.Length, headers["Content-Type"]);
byte[] data = ms.ToArray();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @loribao

  • AsSpan(0, (int)memoryStream.Length) provides a zero-copy slice of the buffer
  • Span<T> is a modern .NET construct that enables high-performance, allocation-free operations
  • This approach avoids creating intermediate objects while maintaining memory safety

The key difference:

  • ToArray() creates a copy of the data in memory
  • GetBuffer() + AsSpan() provides direct access to the underlying buffer without additional allocations

For large data streams, this can result in significant performance improvements and reduced memory pressure.

You can find the optimized version here:
https://github.com/czirok/apps/blob/f237a9b8874eac208b0503f237f2cf2218c070df/src/WebKit.BlazorWebView.GirCore/src/WebKitWebViewManager.cs#L123

@loribao
Copy link
Author

loribao commented Sep 7, 2025

Hi @czirok,

Thanks for the excellent performance optimization suggestions! I implemented the recommended changes and validated them with extensive benchmarking.

Changes Made

Implemented ArrayPool for buffer reuse

  • Replaced direct array allocation with System.Buffers.ArrayPool<byte>.Shared.Rent(bufferSize)
  • Added proper cleanup with ArrayPool<byte>.Shared.Return(buffer) in the finally block

Zero-copy operations with Span

  • Use of new Span<byte>(buffer, 0, read) for efficient buffer slicing
  • Direct span creation avoids unnecessary memory copies

Optimized GLib.Bytes creation

  • Use of GLib.Bytes.New(span) instead of ToArray() to eliminate extra allocations
  • Proper discard pattern with using var bytes

Performance Validation

I created benchmarks in the benchmark/handleurischeme branch of my fork to validate these optimizations. The Version 2 algorithm
shows significant performance improvements, especially for larger data streams.

Benchmark results demonstrate:

  • Reduced memory allocations
  • Improved throughput for large file serving
  • Reduced GC pressure due to the buffer pool

The implementation is now ready and maintains backward compatibility while delivering the suggested performance improvements.

Applied performance optimizations suggested in PR feedback:
- Use System.Buffers.ArrayPool<byte> for buffer reuse instead of allocating new arrays
- Implement Span<byte> for zero-copy buffer operations
- Use GLib.Bytes.New(span) to avoid unnecessary memory copies
- Maintain proper buffer lifecycle with try/finally for ArrayPool.Return

Validated performance improvements with benchmark testing in benchmark/handleurischeme branch.
Comment on lines +122 to +146
const int bufferSize = 64 * 1024;
byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(bufferSize);

var memoryInputStream = Gio.MemoryInputStream.New();
long totalLength = 0;

try
{
while (true)
{
int read = content.Read(buffer, 0, bufferSize);
if (read <= 0)
break;

var span = new Span<byte>(buffer, 0, read);
using var bytes = GLib.Bytes.New(span);
memoryInputStream.AddBytes(bytes);
totalLength += read;
}
}
finally
{
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
}
request.Finish(memoryInputStream, totalLength, headers["Content-Type"]);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer to maintain the workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants