-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
experimental concurrent add #1324
Conversation
@barnacs i'll try it out in a sec, but do you think you could try re-applying these changes on our latest master? (git rebase) there have been a few other perf changes to the dagbuilderhelper code in the meantime |
tried it out, thats really fast, good work! |
2f86e79
to
4c415bd
Compare
+1 to this PR.
I would love to have a benchmark suite and a bunch of workers like in http://build.golang.org/ that can test it out across different "platform configs" (where that here includes choice of fs / devices) |
pipeline: make(chan *dag.Node), | ||
Error: make(chan error), | ||
} | ||
dbh.init() |
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.
instead of this, move the entire construction into a
return NewDagBuilderHelper(dbp.Dagserv, in, dbp.Maxlinks, ncb)
where
func NewDagBuilderHelper(...) {
db := &DagBuilderHelper{
dserv: dbp.Dagserv, dserv: dbp.Dagserv,
in: in, in: in,
maxlinks: dbp.Maxlinks, maxlinks: dbp.Maxlinks,
ncb: ncb, ncb: ncb,
pipeline: make(chan *dag.Node),
Error: make(chan error),
}
hash := hasher()
store := storeWorker(db.dserv)
pin := pinner(db.ncb)
hashedch := make(chan *dag.Node)
storedch := make(chan *dag.Node)
startWorkers(hash, hashWorkers, db.pipeline, hashedch, db.Error)
startWorkers(store, storeWorkers, hashedch, storedch, db.Error)
startWorkers(pin, pinWorkers, storedch, nil, db.Error)
return db
}
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.
then we might as well not have the params struct.
@barnacs this is great! I think lots of go-ipfs can have this sort of pipeline-- we haven't really optimized lots of the codebase. Made some comments above-- pls take a look
|
|
||
const ( | ||
hashWorkers = 1 | ||
storeWorkers = 50 |
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.
I'm willing to bet that if we keep all of these at 1, we will still see significant perf improvements, and we will get slightly cleaner code with not having to worry about multiple different workers.
A few of the go tests fail. looks to be related. |
Alright, a little more detail on my benchmarks:
I ran a few tests with storeWorkers in the range of 2-10, and they all had considerably worse performance. Not sure why that happened. Also, running your branch vanilla i did get a bunch of other numbers that i threw out because they appeared to be outliers, 26 seconds, 33 seconds, 95 seconds, and another one took a whopping three minutes before i killed the operation. This was run on a macbook pro with a PCIe SSD, R/W speeds are generally 700MB/s. and my filesystem is ext4 One other note: on your branch, with storeWorkers equal to the default 50, the first 400-500MB of the file flew by in about a second. the next 400MB or so took about five seconds, and the last 100MB took quite a while. |
@jbenet Thanks for your comments. |
@whyrusleeping that's interesting. I like the 1000MB random file approach; my results using that (linux/ext4/SATA 3 hdd):
|
@barnacs yeah PRs are good, sorry for lack of responsiveness on IRC. github always better for collab things. and totally fine to PR as suggestion/feedback/discussion |
@barnacs would love to see this type of thing land, have any time soon? |
oops, sorry, I forgot about this Actually, the performance boost here seems to be about throwing a lot of concurrent writes at the kernel. I guess mine happened to optimize them, but as @whyrusleeping's results show it really depends on your setup and may even slow things down. I guess the only viable option is then to reconsider durability guarantees and ease up on sync() calls. Maybe expose a sync call explicitly at the lowest data storage level but then do you propagate that all the way up to dag services and change all call sites? Either way, I don't think the approach of this PR is the right one and would result in erratic behaviour so feel free to just close it. |
@barnacs thanks a bunch for bringing this up, it gave a good view into our performance under various loads. I do think that getting the adds out of the main path will be good, maybe having a single extra worker to do the |
In the meantime, would it be possible to add a |
@davidar that would work for offline adds, but if youre doing it with the daemon online, you would have the start the daemon with such an option. I think the ideal stopgap would be to add a nosync field to the config for the datastore in |
@davidar do you mind filing a bug as a feature request for this? |
closing, old PR cleanup time. |
This PR is an experimental concurrent pipeline for "ipfs add"
The idea is to decouple the various cpu / io intensive parts of storing a block into separate stages with a configurable number of workers for each stage.
I would appreciate if some people with different os/fs/physical storage could benchmark this by adding a single large file (~500-1000 MB) and time it against master.
If it turns out to generally improve performance, the implementation could be cleaned up and extended to handle each add operation rather than individual files.
My results on linux/ext4/single hdd (626MB):
master:
concurrent: