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

Introduce an Attribute object to handle the type casting dance #15593

Merged
merged 1 commit into from Jun 13, 2014

Conversation

sgrif
Copy link
Contributor

@sgrif sgrif commented Jun 9, 2014

Depends on #15429 and includes the changes from that PR as well. There's a lot more that can be moved to these, but this felt like a good place to introduce the object. Plans are:

  • Remove all knowledge of type casting from the columns, beyond a
    reference to the cast_type
  • Move type_cast_for_database to these objects
  • Potentially make them mutable, introduce a state machine, and have
    dirty checking handled here as well
  • Move attribute, decorate_attribute, and anything else that
    modifies types to mess with this object, not the columns hash
  • Introduce a collection object to manage these, and reduce allocations

@@ -249,10 +249,12 @@ def relation #:nodoc:
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil, options = {})
defaults = self.class.raw_column_defaults.dup
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
defaults = Hash[self.class.raw_column_defaults.map { |k, v|
Copy link
Contributor

Choose a reason for hiding this comment

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

pretty heavy logic here. could it be extracted to private method like

@attributes = attributes_from_raw_column_defaults

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, not happy with defaults right now, planning much more work on those specifically. Needed to cut the PR at some point. Would rather this stay as close to the original structure as possible for the time being.

@thedarkone
Copy link
Contributor

Do I read it correctly that this will now allocate an Attribute object for every attribute of an AR object?

@sgrif
Copy link
Contributor Author

sgrif commented Jun 10, 2014

And allocate one less string per attribute, one less hash per Base object,
and go through fewer branches on both read and write. Yes.
On Jun 10, 2014 5:22 AM, "thedarkone" notifications@github.com wrote:

Do I read it correctly that this will now allocate an Attribute object
for every attribute of an AR object?


Reply to this email directly or view it on GitHub
#15593 (comment).

@thedarkone
Copy link
Contributor

One hash for multiple Attributes objects might work ok (a mapping in a Hash is usually an object on its own).

Where do you save a string?

@sgrif
Copy link
Contributor Author

sgrif commented Jun 10, 2014

The keys of raw_attributes
On Jun 10, 2014 6:03 AM, "thedarkone" notifications@github.com wrote:

One hash for multiple Attributes objects might work ok (a mapping in a
Hash is usually an object on its own).

Where do you save a string?


Reply to this email directly or view it on GitHub
#15593 (comment).

@thedarkone
Copy link
Contributor

Damn, I remember a PR that attempted to pre-freeze those (frozen strs don't get duped by Hashes), or was that something else?

@sgrif
Copy link
Contributor Author

sgrif commented Jun 10, 2014

Unsure. This is also a first step, which should enable me to reduce object allocations overall once I start tackling @column_types and @column_types_override.

@egilburg
Copy link
Contributor

Do I read it correctly that this will now allocate an Attribute object for every attribute of an AR object?

AR object or AR class? Instantiating an attribute should happen once per column (or user defined) attribute definition, but not every time an AR object is instantiated and attributes populated, right?

@thedarkone
Copy link
Contributor

AR object or AR class?

AR object (not class).

Instantiating an attribute should happen once per column (or user defined) attribute definition, but not every time an AR object is instantiated and attributes populated, right?

No, it seems to be 1x Attribute.new for each attribute of every AR object instantiated.

@sgrif
Copy link
Contributor Author

sgrif commented Jun 11, 2014

That is correct. However, for each Attribute object we gain, we've cut one string allocation when reading attributes. This will enable us to reduce object allocations overall, as we can reduce the number of caches each instance has to keep (dirty checking will put us over the top)

@egilburg
Copy link
Contributor

Does the attribute hold any instance data? If not, it can be a singleton (and this can be optimized later)

@sgrif
Copy link
Contributor Author

sgrif commented Jun 11, 2014

Yes, it does hold instance data. It keeps the value before and after type casting, and is responsible for managing that.

end

def value
@value = type_cast(value_before_type_cast) unless defined?(@value)
Copy link
Member

Choose a reason for hiding this comment

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

Is not better to set @value to nil on the initializer and avoid the call to defined?

Copy link
Member

Choose a reason for hiding this comment

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

I guess the type_cast could return nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My concern was that type_cast can return falsy values, and we'd like to avoid performing that computation multiple times. That said, I can't imagine any branch that ends with nil or false being that expensive. Any thoughts? Worth a benchmark?

Copy link
Member

Choose a reason for hiding this comment

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

I think is worth to benchmark. If it is only an optimisation we need to make clear because I imagine if I change it to use ||= tests will not fail and since there is no information about this optimisation it will be lost.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Benchmark showed defined? is faster when a value is falsy, even when the type does no work but check if it got nil. I do have an explicit test for this case, I'll add a comment as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. Any more feedback?

There's a lot more that can be moved to these, but this felt like a good
place to introduce the object. Plans are:

- Remove all knowledge of type casting from the columns, beyond a
  reference to the cast_type
- Move type_cast_for_database to these objects
- Potentially make them mutable, introduce a state machine, and have
  dirty checking handled here as well
- Move `attribute`, `decorate_attribute`, and anything else that
  modifies types to mess with this object, not the columns hash
- Introduce a collection object to manage these, reduce allocations, and
  not require serializing the types
rafaelfranca added a commit that referenced this pull request Jun 13, 2014
Introduce an Attribute object to handle the type casting dance
@rafaelfranca rafaelfranca merged commit e61e1b3 into rails:master Jun 13, 2014
@sgrif sgrif deleted the sg-attribute branch June 14, 2014 01:18
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.

None yet

5 participants