C# extractor: Improve performance by changing the caching#615
Conversation
| Label existingLabel; | ||
| if (labelCache.TryGetValue(id, out existingLabel)) | ||
| // Look up the Id in the idLabelCache | ||
| if (idLabelCache.TryGetValue(id, out label)) |
There was a problem hiding this comment.
Why is idLabelCache needed? It appears to me that this is dead code, as entityLabelCache.TryGetValue() returns false iff idLabelCache.TryGetValue() returns false?
There was a problem hiding this comment.
After thinking about this, I see that this is indeed not dead code, but can be hit when two different entities share the same ID, and therefor label. Could you put a comment saying something like that here?
There was a problem hiding this comment.
In some ways, it's a bug for two entities to map to the same label, and it could be a performance win if we removed this step.
There was a problem hiding this comment.
As far as I remember, this could happen because two underlying Roslyn symbols could represent the same thing, but I forgot under which circumstances.
There was a problem hiding this comment.
I added some debugging to see where the conflicting labels occurred, which flushed out a few bugs that I've fixed. Overall, the new design gives around a 25% speedup excluding CIL (which doesn't use this code).
C#: Remove unnecessary code from Property.
71872a7 to
6a54a6d
Compare
hvitved
left a comment
There was a problem hiding this comment.
Sounds awesome with the improved performance. I have some suggestions for changes, as well as some questions.
| switch (methodDecl.MethodKind) | ||
| var methodKind = methodDecl.MethodKind; | ||
|
|
||
| if(methodKind == MethodKind.ExplicitInterfaceImplementation) |
| /// <param name="factory">The entity factory.</param> | ||
| /// <param name="init">The initializer for the entity.</param> | ||
| /// <returns>The new/existing entity.</returns> | ||
| public Entity CreateEntity2<Type, Entity>(ICachedEntityFactory<Type, Entity> factory, Type type) where Entity : ICachedEntity |
There was a problem hiding this comment.
CreateEntity2 -> CreateMaybeNullEntity?
There was a problem hiding this comment.
That's not quite the right name for it. It's also used when you want to cache using the entity as the key, e.g.
- When
initis null, - When
Typeis a tuple. In each case the tuple may be different, but you want to index onlyItem1(e.g.GetAlreadyCreated) - When you want to use the same
initto create different entities depending onType. E.g. when you want either aTypeor aTypeRef.
| return true; | ||
| var entity = factory.Create(this, type); | ||
|
|
||
| if(entityLabelCache.TryGetValue(entity, out var label)) |
| private Entity CreateNonNullEntity<Type, Entity>(ICachedEntityFactory<Type, Entity> factory, Type init) where Entity : ICachedEntity | ||
| { | ||
| if (objectEntityCache.TryGetValue(init, out var cached)) | ||
| return (Entity)cached; |
There was a problem hiding this comment.
Yes - it's cached as an ICachedEntity and could theoretically give an InvalidCastException if you try to create it with a different type next time.
There was a problem hiding this comment.
Ah, I failed to see that Entity was a type parameter; misread it as IEntity...
| entity.Label = label; | ||
| return entity; | ||
| } | ||
| else |
There was a problem hiding this comment.
I prefer branches myself -- I moved the return out of the branches instead.
| } | ||
|
|
||
| readonly Dictionary<IId, Label> labelCache = new Dictionary<IId, Label>(); | ||
| readonly Dictionary<IId, ICachedEntity> idLabelCache = new Dictionary<IId, ICachedEntity>(); |
| { | ||
| return factory.CreateEntity(cx, (t1, t2)); | ||
| } | ||
| where Entity : ICachedEntity => factory.CreateEntity2(cx, (t1, t2)); |
There was a problem hiding this comment.
Why not factory.CreateEntity -- (t1, t2) is never null? Same below
There was a problem hiding this comment.
Following on from the comment above - you want to use Tuple.Item1 as the key (the other columns may be different), which is the behaviour of CreateEntity2.
|
|
||
| bool NeedsPopulation { get; } | ||
|
|
||
| object UnderlyingObject { get; } |
There was a problem hiding this comment.
It was quite useful for debugging. I'm minded to keep it in if that's ok?
| get; private set; | ||
| } | ||
|
|
||
| object ICachedEntity.UnderlyingObject => symbol; |
| entityLabelCache[entity] = label; | ||
| DefineLabel(label, id); | ||
| if (entity.NeedsPopulation) | ||
| Populate(null, entity); |
There was a problem hiding this comment.
Why always null argument, and not init as ISymbol?
There was a problem hiding this comment.
Looks like a bug....
Add more tests for duplicated entities, and fix some duplicated entities.
Update the TupleTypes output - some extraneous results gone so it's probably better.
|
This pull request introduces 1 alert when merging d73b28e into a600353 - view on LGTM.com new alerts:
Comment posted by LGTM.com |
apply the implicit-this patch to the remaining go code
Achieves a ~10% single-threaded speedup (77s -> 69s) by changing the caching to entity->label, instead of entity->id & id->label. This removes one cache lookup in the case of a cache hit, and bypasses the ID cache which can be a little slow.