You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I suspect it's the diagnostic output. With cmdstan 2.27.0 if you explicitly enable diagnostic output on the command line, then it takes a similar amount of running time to cmdstan 2.28.1. Whereas the running time of cmdstan 2.28.1 doesn't change if the user specifies a diagnostic output:
cmdstan version
command
running time (sec)
2.27.0
./bench sample data file=bench-data.json random seed=1234
19.326
2.27.0
./bench sample data file=bench-data.json random seed=1234 output diagnostic_file=diagnostic.log
29.611
2.28.1
./bench sample data file=bench-data.json random seed=1234
28.493
2.28.1
./bench sample data file=bench-data.json random seed=1234 output diagnostic_file=diagnostic.log
29.531
Could it be possible that cmdstan 2.28.1 by default is doing all the hard work to format diagnostic output and then throwing it away?
I don't really understand the code, but this part of #987 looks a bit suspicious:
That is a very nice catch @fcostin!! You are spot on.
Commenting out all the diagnostic writes in the stan submodule gets the performance back. So we just need to figure out how to handle this in command.hpp.
In the case where the user does not specify a diagnostic_file, command.hpp is still setting the diagnostic writer to a unique_stream_writer.
Internally, unique_stream_writer is buffering all writes to a local std::stringstream streamer before writing to the output stream.
Curiously, removing the the stringstream buffering doesn't suffice to fix the problem.
If I add an explicit bool toggle to unique_stream_writer to set it into no-op mode, and modify command.hpp to toggle on no-op mode when constructing the diagnostic writer when the user hasn't specified a diagnostic file, then I can observe speedup and running time in line with cmdstan 2.27.0 performance.
it looks like the default stan writer is a no-op writer implementation, but i'm not familiar enough with C++'s type system to suggest a clean patch.
A clean way to do it could perhaps be if diagnostic_writers in command.hpp was a vector of pointers to the base writer type, so it could contain a mix of different concrete writer types, so some could be swapped out for no-op writers.
So is the performance regression due to buffering output after all? That's done to avoid cluttered output under threading. Hopefully we can make the buffer more efficient as this thing is there for a good reason.
Also: Is the performance regression seen for any model which does some more intense computations? Binomial likelihood with normalisation - how is that (e.g. the Bernoulli example model)? Or more complicated stuff like a negative binomial?
I am having the impression that almost trivial models are affected by this... which does not mean we should not fix it, but maybe not at "any cost"?
So is the performance regression due to buffering output after all?
Its due to buffering the output for diagnostics even when the diagnostics file is "". We essentially pay the cost of outputing diagnostics regardless if they are requested or not.
Is the performance regression seen for any model which does some more intense computations?
The more computation there is, the less obvious the performance hit is.
I am having the impression that almost trivial models are affected by this... which does not mean we should not fix it, but maybe not at "any cost"?
I agree that trivial models probably arent the focal point, but I think the solution here should be trivial. Worst case we add a boolean like @fcostin proposes.
The essential problem was that pre-2.28, this line produced a no-op diagnostic writer:
[buffering is] done to avoid cluttered output under threading. Hopefully we can make the buffer more efficient as this thing is there for a good reason.
That makes sense. The problem is not the buffer itself, but that diagnostic output is getting computed and written to the buffer when a user has not requested any diagnostic output. If the user has requested diagnostic output, performance seems comparable to cmdstan 2.27.0 .
When a user does not ask for diagnostic output on this extremely trivial model, cmdstan 2.28.1 spends approximately 50% more CPU instructions than necessary during sampling (!), and these additional CPU instructions are spent string-formatting floating point numbers for diagnostic output, which is then thrown away. i.e. the 50% additional work is pure waste.
My example patch is not minimal or the best solution -- there is no need to remove the buffering in unique_stream_writer.
It would be cleaner to modify command.hpp so it disables diagnostic output by wiring in a no-op diagnostic writer, in the case where a user has not explicitly requested diagnostic output. It should be easy but may need the type of the vector diagnostic_writers in command.hpp to be changed from a vector of unique_stream_writer to a vector of pointers to the base https://github.com/stan-dev/stan/blob/develop/src/stan/callbacks/writer.hpp class
@fcostin think you for the thorough review! Yes I like your solution of making these a pointer up in command.hpp and having a empty_writer for making the writes a no-op. I can put up a PR for this tmrw
EDIT: Actually I think we only need a constructor for writer that takes in a unique_ptr for the stream but does nothing
Reacted by Reuben Fletcher-Costin and Rok Češnovar
Activity
fcostin commentedon Nov 7, 2021
(i'm the same rfc from discourse)
I suspect it's the diagnostic output. With cmdstan
2.27.0if you explicitly enable diagnostic output on the command line, then it takes a similar amount of running time to cmdstan2.28.1. Whereas the running time of cmdstan2.28.1doesn't change if the user specifies a diagnostic output:Could it be possible that cmdstan
2.28.1by default is doing all the hard work to format diagnostic output and then throwing it away?I don't really understand the code, but this part of #987 looks a bit suspicious:cmdstan/src/cmdstan/command.hpp
Lines 501 to 504 in bc4bc94
rok-cesnovar commentedon Nov 7, 2021
That is a very nice catch @fcostin!! You are spot on.
Commenting out all the diagnostic writes in the stan submodule gets the performance back. So we just need to figure out how to handle this in command.hpp.
fcostin commentedon Nov 7, 2021
It looks like the culprit is
stan/src/stan/callbacks/unique_stream_writer.hpp:https://github.com/stan-dev/stan/blob/0e4e2a9f8e4af6fcc6baae5070fc4acddbddd9ad/src/stan/callbacks/unique_stream_writer.hpp
In the case where the user does not specify a diagnostic_file,
command.hppis still setting the diagnostic writer to aunique_stream_writer.Internally,
unique_stream_writeris buffering all writes to a localstd::stringstream streamerbefore writing to the output stream.Curiously, removing the the
stringstreambuffering doesn't suffice to fix the problem.If I add an explicit bool toggle to
unique_stream_writerto set it into no-op mode, and modifycommand.hppto toggle on no-op mode when constructing the diagnostic writer when the user hasn't specified a diagnostic file, then I can observe speedup and running time in line with cmdstan 2.27.0 performance.it looks like the default stan
writeris a no-op writer implementation, but i'm not familiar enough with C++'s type system to suggest a clean patch.A clean way to do it could perhaps be if
diagnostic_writersincommand.hppwas a vector of pointers to the base writer type, so it could contain a mix of different concrete writer types, so some could be swapped out for no-op writers.Here's my dodgy patch:
rok-cesnovar commentedon Nov 8, 2021
Thanks @fcostin for the suggested fix.
I will let @SteveBronder chime in, as he know more about the decisions that went into this.
wds15 commentedon Nov 8, 2021
So is the performance regression due to buffering output after all? That's done to avoid cluttered output under threading. Hopefully we can make the buffer more efficient as this thing is there for a good reason.
Also: Is the performance regression seen for any model which does some more intense computations? Binomial likelihood with normalisation - how is that (e.g. the Bernoulli example model)? Or more complicated stuff like a negative binomial?
I am having the impression that almost trivial models are affected by this... which does not mean we should not fix it, but maybe not at "any cost"?
rok-cesnovar commentedon Nov 8, 2021
Its due to buffering the output for diagnostics even when the diagnostics file is
"". We essentially pay the cost of outputing diagnostics regardless if they are requested or not.The more computation there is, the less obvious the performance hit is.
I agree that trivial models probably arent the focal point, but I think the solution here should be trivial. Worst case we add a boolean like @fcostin proposes.
The essential problem was that pre-2.28, this line produced a no-op diagnostic writer:
cmdstan/src/cmdstan/command.hpp
Line 211 in 998c08f
while this line https://github.com/stan-dev/cmdstan/blob/v2.28.1/src/cmdstan/command.hpp#L502 does not do that.
wds15 commentedon Nov 8, 2021
Sounds like an easy matter then to disable the diagnostic output whenever its not requested.
fcostin commentedon Nov 8, 2021
i agree with @rok-cesnovar's summary.
That makes sense. The problem is not the buffer itself, but that diagnostic output is getting computed and written to the buffer when a user has not requested any diagnostic output. If the user has requested diagnostic output, performance seems comparable to cmdstan 2.27.0 .
When a user does not ask for diagnostic output on this extremely trivial model, cmdstan 2.28.1 spends approximately 50% more CPU instructions than necessary during sampling (!), and these additional CPU instructions are spent string-formatting floating point numbers for diagnostic output, which is then thrown away. i.e. the 50% additional work is pure waste.
My example patch is not minimal or the best solution -- there is no need to remove the buffering in
unique_stream_writer.It would be cleaner to modify
command.hppso it disables diagnostic output by wiring in a no-op diagnostic writer, in the case where a user has not explicitly requested diagnostic output. It should be easy but may need the type of the vectordiagnostic_writersincommand.hppto be changed from a vector ofunique_stream_writerto a vector of pointers to the base https://github.com/stan-dev/stan/blob/develop/src/stan/callbacks/writer.hpp classwds15 commentedon Nov 8, 2021
Oh dear…I reviewed this pr at the time and did not notice. Hopefully @SteveBronder can sort this out quickly.
SteveBronder commentedon Nov 9, 2021
@fcostin think you for the thorough review! Yes I like your solution of making these a pointer up in
command.hppand having aempty_writerfor making the writes a no-op. I can put up a PR for this tmrwEDIT: Actually I think we only need a constructor for
writerthat takes in aunique_ptrfor the stream but does nothingrok-cesnovar commentedon Nov 22, 2021
Closed by #1060