From 2dad035351f055a44d735afdf4074f1b83746fa9 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 6 Jan 2026 16:22:50 +0100 Subject: [PATCH 1/2] Improve performance when fetching Decls. The current implementation in clangsharp_Cursor_getDecl to get a Decl is an N^2 algorithm; getting a Decl for index X means looping through all the previous indices 0-(X-1) first. This is rather slow when dealing with hundreds of thousands of Decl instances, here's a screenshot from Instruments showing 74% of the time spent in clangsharp_Cursor_getDecl: Screenshot 2026-01-07 at 12 47 26 So I added a way in LazyList to use an existing item to get the next item, and then use Decl.NextDeclInContext to take advantage of this, effectively making an N algorithm (which becomes 2N, because we iterate over all the Decls first to count them). My main scenario now runs in ~51 seconds instead of 3m05 seconds, so less that 1/3 of the time. --- sources/ClangSharp/Cursors/Decls/Decl.cs | 11 +++++++- sources/ClangSharp/LazyList.cs | 10 +++++++ sources/ClangSharp/LazyList`1.cs | 36 ++++++++++++------------ sources/ClangSharp/LazyList`2.cs | 32 +++++++++------------ 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/sources/ClangSharp/Cursors/Decls/Decl.cs b/sources/ClangSharp/Cursors/Decls/Decl.cs index 28f54a59..154b4508 100644 --- a/sources/ClangSharp/Cursors/Decls/Decl.cs +++ b/sources/ClangSharp/Cursors/Decls/Decl.cs @@ -35,7 +35,16 @@ private protected Decl(CXCursor handle, CXCursorKind expectedCursorKind, CX_Decl _attrs = LazyList.Create(Handle.NumAttrs, (i) => TranslationUnit.GetOrCreate(Handle.GetAttr(unchecked((uint)i)))); _body = new ValueLazy(() => !Handle.Body.IsNull ? TranslationUnit.GetOrCreate(Handle.Body) : null); _canonicalDecl = new ValueLazy(() => TranslationUnit.GetOrCreate(Handle.CanonicalCursor)); - _decls = LazyList.Create(Handle.NumDecls, (i) => TranslationUnit.GetOrCreate(Handle.GetDecl(unchecked((uint)i)))); + _decls = LazyList.Create(Handle.NumDecls, (i, previousDecl) => { + if (previousDecl is null) + { + return TranslationUnit.GetOrCreate(Handle.GetDecl(unchecked((uint)i))); + } + else + { + return previousDecl.NextDeclInContext; + } + }); _describedTemplate = new ValueLazy(() => { var describedTemplate = Handle.DescribedTemplate; return describedTemplate.IsNull ? null : TranslationUnit.GetOrCreate(describedTemplate); diff --git a/sources/ClangSharp/LazyList.cs b/sources/ClangSharp/LazyList.cs index 463005d0..3a907aaa 100644 --- a/sources/ClangSharp/LazyList.cs +++ b/sources/ClangSharp/LazyList.cs @@ -17,6 +17,16 @@ internal static class LazyList return new LazyList(count, valueFactory); } + public static LazyList Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>(int count, Func valueFactory) + where T : class + { + if (count <= 0) + { + return LazyList.Empty; + } + return new LazyList(count, valueFactory); + } + public static LazyList Create(LazyList list, int skip = -1, int take = -1) where T : class, TBase where TBase : class diff --git a/sources/ClangSharp/LazyList`1.cs b/sources/ClangSharp/LazyList`1.cs index 507eeceb..6f97362a 100644 --- a/sources/ClangSharp/LazyList`1.cs +++ b/sources/ClangSharp/LazyList`1.cs @@ -11,7 +11,8 @@ internal sealed class LazyList<[DynamicallyAccessedMembers(DynamicallyAccessedMe where T : class { internal readonly T[] _items; - internal readonly Func _valueFactory; + internal readonly Func? _valueFactory; + internal readonly Func? _valueFactoryWithPreviousValue; public static readonly LazyList Empty = new LazyList(0, _ => null!); @@ -19,6 +20,14 @@ public LazyList(int count, Func valueFactory) { _items = (count <= 0) ? [] : new T[count]; _valueFactory = valueFactory; + _valueFactoryWithPreviousValue = null; + } + + public LazyList(int count, Func valueFactoryWithPreviousValue) + { + _items = (count <= 0) ? [] : new T[count]; + _valueFactory = null; + _valueFactoryWithPreviousValue = valueFactoryWithPreviousValue; } public T this[int index] @@ -30,7 +39,12 @@ public T this[int index] if (item is null) { - item = _valueFactory(index); + if (_valueFactoryWithPreviousValue is not null) + { + item = _valueFactoryWithPreviousValue(index, index == 0 ? null : _items[index - 1]); + } else { + item = _valueFactory!.Invoke(index); + } items[index] = item; } @@ -56,14 +70,7 @@ public void CopyTo(T[] array, int arrayIndex) for (var i = 0; i < items.Length; i++) { - var currentItem = items[i]; - - if (currentItem is null) - { - currentItem = _valueFactory(i); - items[i] = currentItem; - } - + var currentItem = this[i]; array[arrayIndex + i] = currentItem; } } @@ -76,14 +83,7 @@ public int IndexOf(T item) for (var i = 0; i < items.Length; i++) { - var currentItem = items[i]; - - if (currentItem is null) - { - currentItem = _valueFactory(i); - items[i] = currentItem; - } - + var currentItem = this[i]; if (EqualityComparer.Default.Equals(currentItem, item)) { return i; diff --git a/sources/ClangSharp/LazyList`2.cs b/sources/ClangSharp/LazyList`2.cs index 0e40def2..7e42340e 100644 --- a/sources/ClangSharp/LazyList`2.cs +++ b/sources/ClangSharp/LazyList`2.cs @@ -12,7 +12,8 @@ internal sealed class LazyList _valueFactory; + internal readonly Func? _valueFactory; + internal readonly Func? _valueFactoryWithPreviousValue; private readonly int _start; private readonly int _count; @@ -24,6 +25,7 @@ public LazyList(LazyList list, int skip = -1, int take = -1) _items = list._items; _valueFactory = list._valueFactory; + _valueFactoryWithPreviousValue = list._valueFactoryWithPreviousValue; _start = skip; _count = take; @@ -38,7 +40,15 @@ public T this[int index] if (item is null) { - item = _valueFactory(index + _start); + var idx = index + _start; + if (_valueFactoryWithPreviousValue is not null) + { + item = _valueFactoryWithPreviousValue(idx, idx == 0 ? null : _items[idx - 1]); + } + else + { + item = _valueFactory!.Invoke(idx); + } items[index] = item; } @@ -64,14 +74,7 @@ public void CopyTo(T[] array, int arrayIndex) for (var i = 0; i < _count; i++) { - var currentItem = items[i]; - - if (currentItem is null) - { - currentItem = _valueFactory(i + _start); - items[i] = currentItem; - } - + var currentItem = this[i]; array[arrayIndex + i] = (T)currentItem; } } @@ -84,14 +87,7 @@ public int IndexOf(T item) for (var i = 0; i < items.Length; i++) { - var currentItem = items[i]; - - if (currentItem is null) - { - currentItem = _valueFactory(i + _start); - items[i] = currentItem; - } - + var currentItem = this[i]; if (EqualityComparer.Default.Equals((T)currentItem, item)) { return i; From 12909e5141d942684dc5409fa582cc66004449d8 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 Jan 2026 11:48:17 -0800 Subject: [PATCH 2/2] Apply suggestion from @tannergooding --- sources/ClangSharp/LazyList`1.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/ClangSharp/LazyList`1.cs b/sources/ClangSharp/LazyList`1.cs index 6f97362a..789c9576 100644 --- a/sources/ClangSharp/LazyList`1.cs +++ b/sources/ClangSharp/LazyList`1.cs @@ -42,7 +42,9 @@ public T this[int index] if (_valueFactoryWithPreviousValue is not null) { item = _valueFactoryWithPreviousValue(index, index == 0 ? null : _items[index - 1]); - } else { + } + else + { item = _valueFactory!.Invoke(index); } items[index] = item;