-
Notifications
You must be signed in to change notification settings - Fork 1
Performance Improvements #117
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
Conversation
|
Thanks @matthewmcgarvey. I have some thoughts about this PR: Lazy initializeNice catch, the change saves some memory and the code keeps simple. Suggestion: You can use getter buffer : String::Builder do
String::Builder.new
endAbout 2 & 3About 2, I'm not sure if thas brings some perfomance improvement, so it would be nice some benchmark isolating this to check it out. In my tests it didn't effect the benchmark results. About 3, in the past I tried this approach on Benchmarkclass WithConditional
getter buffer : String::Builder do
String::Builder.new
end
def run(value)
if value.includes?('"')
buffer << value.gsub('"', """)
else
buffer << value
end
end
end
class WithoutConditional
getter buffer : String::Builder do
String::Builder.new
end
def run(value)
buffer << value.gsub('"', """)
end
end
Benchmark.ips do |x|
x.report("With") do
with_conditional = WithConditional.new
with_conditional.run("test")
with_conditional.run("some long string here to test the method")
with_conditional.run("some long \" string here to test the method")
with_conditional.run("btn btn-red")
with_conditional.run("card shadow")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
with_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
end
x.report("Without") do
without_conditional = WithoutConditional.new
without_conditional.run("test")
without_conditional.run("some long string here to test the method")
without_conditional.run("some long \" string here to test the method")
without_conditional.run("btn btn-red")
without_conditional.run("card shadow")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
without_conditional.run("hidden peer-checked:flex w-full h-full mask items-center justify-center border-[2px] ring-[2px] ring-offset-1 ring-base-content rounded-full")
end
endResults: Attributes cachingAbout attributes caching, I gave a try in this project in the past but I reverted the chances since I need to ensure it to be thread safe using a kind of lock/sync. Something like this: @[Experimental]
module Blueprint::HTML::AttributeCaching
CACHE_MUTEX = Mutex.new
private CACHE = {} of UInt64 => String
private def append_attributes(attributes : NamedTuple) : Nil
return if attributes.empty?
attributes_hash : UInt64 = attributes.hash
CACHE_MUTEX.synchronize do
if cached_attributes = CACHE[attributes_hash]?
@buffer << cached_attributes
else
# ... processes attributes here
CACHE[attributes_hash] = processed_attributes
@buffer << processed_attributes
end
end
end
endSo I don't want this complexity in the project right now because it needs some maintenance and advanced tests. Since the users can implement their own module, they can copy & paste my or yours attribute cache implementation in their projects. About
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I detailed my review in previous comments. So, if you agree, we need to keep the lazy load/memoization on @buffer.
src/blueprint/html.cr
Outdated
| @buffer : String::Builder? | ||
|
|
||
| private def buffer : String::Builder | ||
| @buffer ||= String::Builder.new | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improvement
| @buffer : String::Builder? | |
| private def buffer : String::Builder | |
| @buffer ||= String::Builder.new | |
| end | |
| getter buffer : String::Builder do | |
| String::Builder.new | |
| end |
benchmark/benchmark.cr
Outdated
| if blueprint_html != ecr | ||
| puts "Blueprint::HTML #{Blueprint::VERSION} and ECR are different".colorize(:red) | ||
| puts "Diff:" | ||
| Diff.diff(ecr, blueprint_html).each do |chunk| | ||
| print chunk.data.colorize( | ||
| chunk.append? ? :green : chunk.delete? ? :red : :default) | ||
| end | ||
| exit 1 | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts
I think we don't need to sofisticate this.
shard.yml
Outdated
| diff: | ||
| github: MakeNowJust/crystal-diff | ||
| branch: master |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts
Since we don't want to sofisticate benchmark tests, we would not need this anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem
Cache attributes brings some complexity that we don't want in the project right now because we need some thread-safe code and advanced tests.
And I think changes in parsing attribute name/value don't bring sensible improvements in performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts
I don't think it brings sensible improvements to benchmark resulst. But if we have some benchmark showing the benefits we can merge this.
|
Thanks for looking it over. I should have just made a draft PR because I figured this would not be desired as-is. I'll isolate the code without the caching and verify there are improvements. |
|
@matthewmcgarvey about attributes caching, you motivated to work on a new version of my previous work with thread-safe code. This is the PR: #120 |
d3274cc to
9fe215f
Compare
|
You were right. None of the other stuff mattered as much as the caching. I know I ran performance tests on gsub for checking first, but it might have been just straight up benchmarking the string directly instead of this library and I can't remember the results. I found no change when implementing it in the library and running the benchmark. |
This does a few things to help improve performance but there is one main improvement.
(minor change: added diff to print an understandable message when benchmark strings vary between blueprint and ecr)
Results: This almost doubles the speed of the library making it a little over 3x slower than ECR where it was 6x slower before. This would be more if sub-components were also used in the benchmark, but not too different.