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

Freeze absolute paths for reduce allocations on file operations #125

Merged
merged 1 commit into from
Jun 28, 2020
Merged

Freeze absolute paths for reduce allocations on file operations #125

merged 1 commit into from
Jun 28, 2020

Conversation

casperisfine
Copy link
Contributor

@casperisfine casperisfine commented Jun 28, 2020

Most of the File methods cast the paths it receives with FilePathValue / rb_get_path which ultimately delegate to rb_new_str_frozen (aliased as rb_str_new4).

rb_new_str_frozen could be experessed as str.frozen? ? str : str.dup.

So by freezing the paths passed to the various File. methods, we
save at least one useless allocation each time, unless the string encoding
is incompatible with the file system.

s = __FILE__
alloc = GC.stat[:total_allocated_objects]
File.realpath(s)
p GC.stat[:total_allocated_objects] - alloc # => 3

s = __FILE__.freeze
alloc = GC.stat[:total_allocated_objects]
File.realpath(s)
p GC.stat[:total_allocated_objects] - alloc # => 2

lib/zeitwerk/loader.rb Outdated Show resolved Hide resolved
@fxn
Copy link
Owner

fxn commented Jun 28, 2020

I've checked with Module#autoload too. It saves three allocations!

s = __FILE__
alloc = GC.stat[:total_allocated_objects]
Object.autoload(:A, s)
p GC.stat[:total_allocated_objects] - alloc # => 5

s = __FILE__.freeze
alloc = GC.stat[:total_allocated_objects]
Object.autoload(:B, s)
p GC.stat[:total_allocated_objects] - alloc # => 2

Most of the `File` methods cast the paths it receives with `FilePathValue`
which ultimately delegate to `rb_new_str_frozen`.

`rb_new_str_frozen` could be experessed as `str.frozen? ? str : str.dup`.

So by freezing the paths passed to the various `File.` methods, we
save at least one useless allocation each time, unless the string encoding
is incompatible with the file system.

```
s = __FILE__
alloc = GC.stat[:total_allocated_objects]
File.realpath(s)
p GC.stat[:total_allocated_objects] - alloc # => 3

s = __FILE__.freeze
alloc = GC.stat[:total_allocated_objects]
File.realpath(s)
p GC.stat[:total_allocated_objects] - alloc # => 2
```
@casperisfine
Copy link
Contributor Author

I've checked with Module#autoload too. It saves three allocations!

Yeah, autoload calls realpath, which itself allocates a bit too much. I'm currently working on upstream PRs to improve that (ruby/ruby#3267).

But it seems like all File operation always allocate at least one object:

s = __FILE__.freeze
alloc = GC.stat[:total_allocated_objects]
File.exist?(s)
p GC.stat[:total_allocated_objects] - alloc # => 1

It's not allocating a String though, I'm trying to pinpoint that alloc. Anyway it's a bit unrelated.

@fxn
Copy link
Owner

fxn commented Jun 28, 2020

Perfect!

@fxn fxn merged commit c5b6582 into fxn:master Jun 28, 2020
@casperisfine
Copy link
Contributor Author

But it seems like all File operation always allocate at least one object:

Ok so self answer, even calling an empty method cause 1 alloc, so 1 is the minimum.

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