<Eiam>
Hi good folks in #jruby, a superset of the other good folks in #ruby. I am currently migrating a project from ruby to jruby and just trying to map my gems over and get things installed
<Eiam>
currently, my understanding is ruby 2.5.1 is not yet supported, I'm locked to 2.5.0 with the 9.2.0.0 engine
<Eiam>
is that accurate?
<Eiam>
second, I'm trying to bring over my favorite debugger, pry. Locally I can "gem install pry-debugger-jruby" and it installs without issue, but when I include the same thing in my gemfile it fails ,"could not find pry-debugger-jruby" in rubygems repository or installed locally. which is a bit weird, since i just installed it. "gem list" clearly shows "pry-debugger-jruby (1.2.1 java)"
<lopex>
enebo: so I'm wondering about this open addressing hash
<lopex>
enebo: if you allocate a bunch of buckets in a row
<lopex>
they should still be in the same cache lines right ?
<lopex>
the bucket introduce indirections sure
<enebo>
well memory locality of n objects linked by references is almost always going to be worse than just relying on a primitive array
<lopex>
yeah
<enebo>
I fully expect to see improved performance in those benchmarks with the last round of changes
<lopex>
sure
<lopex>
enebo: but you should see better diff when making puts interleaved by other allocations
<enebo>
GC handbook talks about some collectors relocating on locality of object references but I don't think any JVM collectors do it.
<lopex>
so the buckets are more scattered
<enebo>
The handbook made it sounds very difficult
<lopex>
and not that indirections are dominant
<enebo>
lopex: yeah in real life we are not hashing the same thing over and over in a loop so it should only improve
<enebo>
I guess I don't know how much it matters in Java though
<lopex>
enebo: oh, how many locality metrics are there ?
<enebo>
After all we have objects all over the place all the time
<lopex>
I guess more than one
<enebo>
so having good locality is good but it may not be visible in the sea of cache misses
<enebo>
I have no idea though
<lopex>
so we go back to profiling tools
<enebo>
yep
<enebo>
the notion we are able to control this very much at the level we are at probably is wishful thinking but I do expect that primitive array version to microbench faster
<lopex>
enebo: and funny thing is that all is not for von neuman fault
<lopex>
just ram
<lopex>
and that fast flip flop takes up to 6 transistors
<lopex>
and ram bit is just one transistor and a capacitor
<ChrisBr>
yeah right! But we have a max load factor of 50%
<enebo>
lopex: how much is object record sizewise: I remember 23 bytes?
<enebo>
or words
<lopex>
enebo: hmm,
<lopex>
class is a pointer
<lopex>
hash is always 32 bit
<lopex>
and bit mask integer
<lopex>
enebo: depends how you count
<enebo>
there is also compressed storage now too for fields
<enebo>
in this case though that would not apply
<lopex>
so the ref itself, class, 32 bit hashcode, and an int for metadata
<enebo>
ChrisBr: Ignoring growing array you can just try different linear sizes and see when it is better
<lopex>
enebo: not sure how packed they are wrt alignement
<lopex>
enebo: and a size for arrays
<enebo>
lopex: but for this bucket we basically are referencing object and using a long
<enebo>
so those will just align
<lopex>
enebo: and next, prev
<lopex>
and key
<lopex>
and value
<enebo>
lopex: yeah but all aligned
<lopex>
if you're talking about a bucket
<enebo>
yeah linked list is significant
<ChrisBr>
enebo: so you mean trying with smaller inital size?
<lopex>
yeah, words are always aligned
<lopex>
in java
<enebo>
ChrisBr: just saying 15 seems like it is clearly slower at linear
<enebo>
ChrisBr: half makes me wonder if MRI even picked a reasonable size
<enebo>
15 seems like a human just picked it
<enebo>
I guess it is 1 less than 16
<lopex>
enebo: afaik mri is insane and it once had 2x
<lopex>
enebo: so guaranteed collistions
<lopex>
not sure what it does now
<lopex>
ChrisBr: java is 0.75
<enebo>
you know I did not really ever look at this PR
<ChrisBr>
enebo: size it 16 :) did I write 15 ? :/
<enebo>
so the RubyHashEntry is still there so my comment about removing it makes no sense since it still exists
<lopex>
ChrisBr: do you also use those MRI magic exp numbers etc ?
<enebo>
ChrisBr: oh well I naively assumed testing 15 and 16 was showing boundary difference unless 16 is 0 elements to 15?
<enebo>
I guess maybe I should not talk too much right after a run though
<enebo>
you probably do mean 0-15 elements is linear where 0 is pretty much no search but still linear
<ChrisBr>
right, so I test 16 elements (0-15 - liner search) and then I test 16 elements (hashing)
<enebo>
I guess I would try 8
<ChrisBr>
yeah, right
<enebo>
and maybe even 4 but just to see
<ChrisBr>
lopex: magic exp numbers?
<ChrisBr>
like the 16 for linear search?
<lopex>
ChrisBr: oh I mean the "feature" arrays
<enebo>
this version allocates a rubyhashentry every time you search?
<ChrisBr>
for put not, for get I create a temporary RubyHashEntry to return it
<ChrisBr>
otherwise I would need to change the "interface" everywhere
<ChrisBr>
but I only store the RubyObjects and not the RubyHashEntry's
<enebo>
ChrisBr: yeah I see. I bet that is showing up in the get bench. This version is trading search locality with an allocation
<ChrisBr>
so it is "just" a temporary object, but sure we would also need to get rid of it at some point
<enebo>
ChrisBr: a benchmark play thing would be to reuse a single instance to see how much it changes get performance
<enebo>
ChrisBr: that might make us consider whether we want to change the API or not
<ChrisBr>
single instance you mean retrieving always the same key?
<lopex>
just using the same entry as a singleton
<enebo>
no I mean allocation a since RubyHashEntry and basically just set the values into that single instance
<lopex>
for the benchmark
<ChrisBr>
ah right, ok
<ChrisBr>
I can try that
<enebo>
that may have been a garbled sentence :P
<enebo>
but so long as you could parse it
<lopex>
enebo: actually what interface sohuld be changed ?
<ChrisBr>
lopex: interface was maybe the wrong word
<ChrisBr>
method signature
<lopex>
ChrisBr: then do it :P
<ChrisBr>
this is still "quick & dirty" ;)
<enebo>
lopex: I don't know but if that allocation is big part of get perf then maybe we consider changing API
<enebo>
ChrisBr: this is just an experiment :)
<enebo>
an evil one
<lopex>
ChrisBr: the entry thing should not be transparent for internal jruby api
<lopex>
ChrisBr: we could construct it for JI though
<lopex>
enebo: seems fair enough ?
<enebo>
lopex: not sure I got that. I thought you meant since it is internal we can change it
<lopex>
enebo: yes, but java allow to iterate over entries afaik ?
<enebo>
lopex: oh I see
<enebo>
lopex: ah yeah not sure how iteration would work but I am not sure how much we care if it is fast or slow in JI case
<lopex>
enebo: yeah it's not a concern for us wrt jruby api
<enebo>
lopex: I guess internally we can iterate over it in a less boxed way
<enebo>
lopex: but for implementing Map I guess I don't know
<lopex>
enebo: java's Map.Entry and entrySet
<enebo>
lopex: but don't we already box into some Entry object Java wants in that case?
<enebo>
so RubyHashEntry is not really important in that case is it?
<lopex>
enebo: yeah, at very least we reconstruct the thing
<lopex>
enebo: yes
<enebo>
lopex: but don't we already or does RubyHashEntry implement Entry?
<lopex>
enebo: I mean agreed
<enebo>
lopex: ok
<lopex>
enebo: yeah, good catch, and probably I made it so
<enebo>
lopex: so that is a concern
<lopex>
enebo: we're old
<lopex>
enebo: why ?
<lopex>
enebo: I mean only for JI
<enebo>
well maybe not too bad a concern but now enumeration from Java needs to allocate no instances
<enebo>
with this change it will get slower
<enebo>
internally so long as we don't use that then it won't affect "ruby"
<lopex>
enebo: depends on the usage yes
jrafanie has quit [Quit: My MacBook has gone to sleep. ZZZzzz…]
<enebo>
but anyone in JI land who passes it into java will see it slow down. So by concern i mean we should see how bad that is
<enebo>
It might not be bad enough to care
<lopex>
enebo: unless it's mitigated by the gains
<enebo>
true
<enebo>
I can see fetch is quite a bit faster with the new version
<lopex>
enebo: hmm, good call, it might be the reason java havent introduced open addressing
<lopex>
right ?
<enebo>
no hmm
<enebo>
it is slower on fetch
<enebo>
I read it backwards
<enebo>
so I bet allocation is the difference
<lopex>
definitely
<lopex>
enebo: lolz, actually java api's usage might be the reason java will not introduce open addressing
<enebo>
lopex: yeah possibly
<lopex>
and it will be the exposure of the entry type
<enebo>
Java is a big ship with a lot of cargo
<lopex>
enebo: just like the hashcode
<lopex>
enebo: and tonn of momentum
<lopex>
a ton
<enebo>
I wonder how well graal deals with this microbench
<lopex>
enebo: you mean excape analysis ?
<enebo>
yes
<lopex>
enebo: I sort of though hotspot would make it work
<lopex>
thought
<enebo>
In case of temp alloc instance
<enebo>
yeah it seems very limited in use
<enebo>
shit is ripped out from method which makes it and it is read only
<enebo>
Even if graal kills it with perf I am not sure we can just roll with it or not
<ChrisBr>
enebo: lopex: for get I also do not use the hash anymore because I don't store it anymore! Do you think it makes a difference to still calculate the hash and compare instead of the values?
<lopex>
enebo: I dont know, print inlining might give some insight
<enebo>
ChrisBr: maybe. It is a difference
<lopex>
ChrisBr: I think you cannot rely on hash perf being cheap
<lopex>
er
<enebo>
some objects do deep hash calcs right?
<lopex>
yes
<enebo>
like struct?
<lopex>
but wait
<lopex>
I'm confused
<lopex>
you get hash, find a bucket and when do you use it the second time ?
<lopex>
same for open addressing right ?
<lopex>
enebo: what you do is equals then
<ChrisBr>
so for the key I want to insert / fetch I need to calc hash and bucket, yes
<ChrisBr>
but for everything what is already there, I don't have the hash anymore (because I only store the key & value object)
<ChrisBr>
so I would need to calc the hash for every element (for linear search for every iteration)
<lopex>
doh, it's for rehash
<lopex>
enebo: ^^
<lopex>
and just rehash ?
<enebo>
caching hash?
<lopex>
yes
<lopex>
and copy
<lopex>
seems right
<enebo>
just to know whether it needs to move?
<enebo>
it compares against fresh hashCode?
<lopex>
for rehash ?
<enebo>
yeah
<enebo>
I am asking you
<enebo>
Seems like RubyString could really cache hashcode and invalidate if contents change
<enebo>
it recalcs every hashCode call
<ChrisBr>
before we stored the hash inside the RubyHashEntry
<enebo>
unrelated
<ChrisBr>
then for fetching, putting, deleting etc we compared the hash and then equals
<lopex>
enebo: no, just to copy the hashes
<enebo>
ChrisBr: lopex I thought that was just used for rehash
<ChrisBr>
because we already had the hash for the new elements and existing ones so it is cheaper to compare than the equals
<lopex>
since rehash is a form of copy :P
<ChrisBr>
now we don't store them anymore so we would need to calculate the hash on the fly
<enebo>
ChrisBr: so you might want to use a String as part of bench in addition to Fixnum since that recalcs hashCode all the time
<lopex>
enebo: aaaah
<enebo>
the fixnum one is pretty much free
<lopex>
enebo: it's used for fast skip on bucket search
<ChrisBr>
right
<lopex>
er, it doesnt make sense
<lopex>
wait
<ChrisBr>
that is the question: is it still fast skip if we need to calc the hash on the fly or do we just do an equals
<lopex>
yeah, actually when bucket(hash1) != bucket(hash2) and hash1 != hash2
<lopex>
bucket loosed information yes
<lopex>
jeeze it was so long ago
<lopex>
well, it's a fast skip after all
<lopex>
ChrisBr: yeah, since hash for ruby potentially calls dozens of ruby objects by method call
<lopex>
ChrisBr: or
<enebo>
I have to go to dinner now but hopefully you two will figure this out I guess removing the hash comparison from internalKeyExist makes me wonder what happens if key contents change
<lopex>
ChrisBr: if we minimize the collisions by increasing load factor we can get rid of it
<lopex>
enebo: ^^
<lopex>
enebo: and thar what mri seems to be doing
<enebo>
although that question makes me think finding the same object if object state exists is bizarre in first place
<lopex>
enebo: ^^
<lopex>
enebo: makes sens ?
<lopex>
*sense
<enebo>
lopex: higher factor means more buckets so less items likely to be in same one
<enebo>
lopex: more buckets compared to number of entries in hash
<enebo>
that was awkward way of saying that
<enebo>
I mean relationship as more entries are added more buckets would be added compared to using a lower load factor