--
You received this message because you are subscribed to the Google Groups "ninja-build" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ninja-build...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
(I would just like to interject that if a binary protocol is warranted, protobuf might be a perfect way to define the format, especially if two different codebase are communicating via it. *goes back to lurk mode*)
I really like the idea of optimizing software by doing less work.
All of these performance problems are simplified if the input build
files are smaller, so any change we can make to make the input
build.ninja files small would solve performance problems without
needing to write any tricky software. I appreciate that kati is
already very smart though!
> Does this sound good? If maintainers are OK with this, I'll complete
> the implementation and send a PR. Current implementation is just for
> this experiment and I guess I'll need to rewrite almost
> everything. The implementation idea in my mind is similar to Python's
> .pyc files for .py files. Ninja tries to open a file like
> .ninja_state.build.ninja (or .ninja_state.<.ninja specified by -f flag>).
> If this file is newer than the build.ninja and ninja successfully
> deserializes the data, it can skip the parse step. Ninja will output
> this file when parsing happens.
I think Nico's advice (that we should ensure parsing time is really
the problem) is good.
But if a binary format is necessary, what do you think about defining
a binary file format and having kati output it directly?
The original philosophy of ninja is that the generator program does as
much work as possible, so having ninja parse a text file to produce a
binary file and then read the binary file later seems like extra work.
> There are two further optimization ideas. I'm not sure if they are good
> ideas. They would be more intrusive to ninja's design than just
> introducing serialization format, but let me share:
>
> - Current implementation deserializes all data at ninja's start-up. I
> think we can make NULL build even faster by delaying deserialization
> of BindingEnv. Maybe we can introduce something like
> SerializedBindingEnv, but I'm not sure.
Can you describe this idea more? Don't you need the full build graph
to determine what to run?
> - I'm also wondering if introducing interned strings to ninja helps. We
> could use offsets to a serialized string pool instead of std::string
> as keys of BindingEnv.
Do lookups in BindingEnv show up in profiles?
Can you paste a profile just so I can follow along in the code to
understand this problem better?
...I probably should try making a kati+android build so I can better
understand all of this, hmm.
On Fri, Jan 8, 2016 at 2:47 AM, Evan Martin <mar...@danga.com> wrote:I really like the idea of optimizing software by doing less work.
All of these performance problems are simplified if the input build
files are smaller, so any change we can make to make the input
build.ninja files small would solve performance problems without
needing to write any tricky software. I appreciate that kati is
already very smart though!Yeah, build.ninja generated by kati is huge. Currently, kati generates a rule for each non-phony targets. I have tried a few easy-to-implement approaches to reduce the size of build.ninja, but there seemed not to be a low-hanging fruits. I still have an idea, but I'm not sure if it's feasible yet. I decided to hack ninja first because it's easier and I thought improving ninja could also benefit other projects while kati will be replaced in favor of Soong eventually.Speaking of Soong, Soong will generate much smarter build.ninja. But still Android platform is a larger project than Chromium. A real Android target has ~4.4x build targets than Chromium has. So, I'm guessing optimizing ninja's parsing would make a visible difference even if we reduce the size of build.ninja.Some numbers: https://docs.google.com/spreadsheets/d/1vfXxJ4ZlDkWpoKIzifmJ_kTvbg1PLZW0eHJ1rLU39Oo/edit#gid=0
> Does this sound good? If maintainers are OK with this, I'll complete
> the implementation and send a PR. Current implementation is just for
> this experiment and I guess I'll need to rewrite almost
> everything. The implementation idea in my mind is similar to Python's
> .pyc files for .py files. Ninja tries to open a file like
> .ninja_state.build.ninja (or .ninja_state.<.ninja specified by -f flag>).
> If this file is newer than the build.ninja and ninja successfully
> deserializes the data, it can skip the parse step. Ninja will output
> this file when parsing happens.
I think Nico's advice (that we should ensure parsing time is really
the problem) is good.Understood. Nico, could you share your idea to improve the text parser? I think the ".ninja parse" phase actually does two tasks. Ninja parses build.ninja and constructs build graph at this stage. I'm guessing we need to optimize both of them to achieve 2x speedup.
parse time (s) (*2) | Nico's "bench" (s) (*3) | deserialization time (s) | deserialization v2 (s) (*4) | |
Chromium | 0.328 | 0.143 | 0.112 | 0.092 |
AOSP (aosp_arm) | 2.017 | 1.028 | 0.684 | 0.418 |
Real Android target | 3.467 | 1.715 | 1.068 | 0.627 |
I suppose we could have a separate tool that converts textual manifests to binary manifests that scripts (and generators?) could call, and ninja 2.0 itself would only support binary manifest files (?)
Hi all,
I wanted to throw out one idea for what the binary serialization format
could look like.
It could literally just be the memory region backing the build graph
(allocated using a bump pointer allocator) dumped out to disk.
Thank you for collecting this data! 0.6s is certainly much faster than 1.7s, so binary manifests (sadly [1]) look very interesting. It does make things more complicated, though -- I wish the parser would be faster, but getting speeding it up another 2.5x seems unlikely.How fast are complete empty Android builds with your patch (i.e. not just parsing, but also stat()ing, deps loading, and so on)? (I'm asking to find out if even larger reorganizations might be necessary.)
How long does serialization take? Someone told me they had a 1GB manifest file (that I used to benchmark my parser changes a bit back then) -- I suppose serializing large manifests would take a while?
In general, I prefer if programs are consistently fast, not sometimes slow and sometimes fast -- so I agree with Evan it'd be nice to define a binary manifest file, so that build.ninja could either be in either text or binary format. (I don't think we should depend on protobuf, though. I do think having a "dump binary manifest as textual manifest" thingy would be useful.)For the serialization format: Have you tried having a string table at the front and then have strings only be references to that string table? That might make deserialization even faster (but it's probably not easily doable with ninja's current internal graph structure -- but if the text parser hashed up strings, it might become feasible. It'd also require programs that generate ninja to not just stream out data, but that might be ok. But maybe it's not worth it.
We could do the .ninja_state thing in addition to that (if build.ninja is in text format, write a binary version of it to .ninja_state and use that if available, like you say), but I'm not sure it's worth the complications. (I'm not sure if comparing mtime on build.ninja and .ninja_state is good enough: A generator might only touch a subninja file if the toplevel manifest doesn't change; and so on.) On the other hand, if we did this we could not expose the binary format at first and iterate a bit on it until we're happy with it, before we make it available as public API.
I'm not sure what the desired end state would be: If we ever do a 2.0, would we drop support for textual manifests? I sometimes create ninja files from shell scripts, and creating binary files that way is more difficult. But having a slow and a fast format is opposed to ninja's "fast and simple" philosophy -- I suppose we could have a separate tool that converts textual manifests to binary manifests that scripts (and generators?) could call, and ninja 2.0 itself would only support binary manifest files (?)
NULL build (sec) | save state (sec) | saved file size (MB) | |
Base | 4.657 | 0 | 0 |
Mine | 2.286 | 1.807 | 374 |
Peter's | 1.668 | 5.78 | 1469 |
NULL build (sec) save state (sec) saved file size (MB) Base 4.657 0 0 Mine 2.286 1.807 374 Peter's 1.668 5.78 1469 Peter's code completely eliminated parsing/deserialization time. The downside was the saved state file is big (note the input ninja file size is 446MB) and saving state takes longer time.