Skip to content

NHibernate boxing #2187

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

Closed
DmitryMak opened this issue Jul 8, 2019 · 9 comments
Closed

NHibernate boxing #2187

DmitryMak opened this issue Jul 8, 2019 · 9 comments

Comments

@DmitryMak
Copy link

I noticed that NHibernate boxes value types. This applies to built-in types like System.Guid and custom structs.

class Person {         <-- mapped entity
    private Guid _id;  <-- boxed copy will be generated
    private Age _age;  <-- custom struct, boxed copy will be generated
}

After restoring a Person instance, there will be one copy of Guid and Age baked into Person (as expected of struct fields). But there will also be at least one boxed instance of Guid and Age floating around on the heap. When loading a lot of entities, NHibernate will create a significant amount of boxed values. The values will have to be collected, hopefully as part of Gen0 collection. Classes that implement IUserType (or ICompositeUserType) are forced to box because they have to return Object, e.g.:

public interface ICompositeUserType {
    Object NullSafeGet(DbDataReader dr, string[] names, ...)
    ...
}

Is this by design or is it theoretically possible to avoid boxing? And if so, are there any plans for this feature? And maybe there a better place to ask this question?

@oskarb
Copy link
Member

oskarb commented Jul 9, 2019

NHibernate started before generics existed so there was a lot of "object", and it was also based on Java's Hibernate, and Java didn't have generics until much later. Also, there is a lot of reflection involved.

NHibernate will also hold a copy of all retrieved values in the session cache in order to do dirty checking. Perhaps these are the boxed items you mention?

I think it might be useful if you analyse further and see if you can find out what changes would be necessary to avoid the boxing, and also if the end result is actually better for performance or not.

This forum is intended as an issue tracker. A more open ended question like this I think belongs in the NHibernate development mailing list, so please continue there.

@oskarb oskarb closed this as completed Jul 9, 2019
@DmitryMak
Copy link
Author

DmitryMak commented Jul 10, 2019

Holding a copy should not require boxing, at least in theory. If cache holds entities (C# classes), their struct fields are baked in and not boxed. Even if cache holds C# structs directly, it does not automatically mean that they need to be boxed. Only if they were cast to Object. I'm not sure if this casting can be avoided internally. But I think it's worth mentioning that holding a copy does not automatically imply boxing, in general sense.

In any case, NHibernate requires users to cast to Object (which will cause boxing) in at least 2 places:

  • NullSafeGet and NullSafeSet methods of IUserType and ICompositeUserType operate on and return Objects
  • results of SetProjection criteria return Object[] or Object[][] that later needs to be unboxed into primitives like Int32. Basically all SQL aggregate functions

Aside from these two, my experiments show that NHibernate boxes primitives internally. Loading a Person entity from my example will cause Guid and Age to be boxed.

I may not be able to dig deeper into the issue and provide specific examples where boxing happens (due to time constraints). But I just want this conversation to be available to others if they search for 'NHibernate boxing'.

@hazzik
Copy link
Member

hazzik commented Jul 10, 2019

Boxed values are in the entity state array (it is object[]) You can avoid this by loading entities as read only (it will remove the state), for example.

@DmitryMak
Copy link
Author

Boxed values are in the entity state array (it is object[]) You can avoid this by loading entities as read only (it will remove the state), for example.

Entity can not be boxed if its a C# class, it is already on the heap. Structs that are members of the entity (Guid and Age in my example) are also not boxed, they are just values that are part of the instance memory. Boxing happens when a struct (value type, Guid or Age) gets cast to Object. I think boxing happens when or before entity gets hydrated. At least this is what is suggested by NullSafeGet and NullSafeSet because they force casting to Object. I don't know if these interfaces are used internally though.

@oskarb
Copy link
Member

oskarb commented Jul 10, 2019

@DmitryMak The entity state array contains the loaded state of the entities, i.e. the values. Not the entities. If you have an entity with two integer properties, the array will contain two boxed integers.

@nhibernate nhibernate locked as off-topic and limited conversation to collaborators Jul 10, 2019
@nhibernate nhibernate unlocked this conversation Jul 10, 2019
@hazzik
Copy link
Member

hazzik commented Jul 10, 2019

I second what Oskar said.

@DmitryMak
Copy link
Author

@DmitryMak The entity state array contains the loaded state of the entities, i.e. the values. Not the entities. If you have an entity with two integer properties, the array will contain two boxed integers.

Thanks for the explanation! Do you think the entity state array is the only source of boxing? Wouldn't NullSafeGet be called even in read-only session? Same for SQL aggregate functions. I think there are multiple sources of boxing and I will try to share a sample with you later (with a readonly session).

@hazzik
Copy link
Member

hazzik commented Jul 10, 2019

NullSafeGet is the source of boxing, but this boxed value would ultimately end up in the state array, and this is why you see it in your heap.

@DmitryMak
Copy link
Author

NullSafeGet is the source of boxing, but this boxed value would ultimately end up in the state array, and this is why you see it in your heap.

I see. My understanding was that read-only session would not have entity state array. But NullSafeGet would still be called for read-only session, so there will be boxing in this scenario. Its just that these boxed values will be 'unrooted' as oppose to boxed values in regular session that will be rooted by entity state array.

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

No branches or pull requests

4 participants