env_logger icon indicating copy to clipboard operation
env_logger copied to clipboard

Slowness

Open dpc opened this issue 6 years ago • 11 comments

As soon as you enable RUST_LOG, even for just a small module that logs infrequently, the performance of your program is going to go drastically downhill.

Example: https://i.imgur.com/al4BJUn.png

+   94.04%     0.39%  rust-bitcoin-in  rust-bitcoin-indexer  [.] <serde_json::read::IoRead<R> as serde_json::read::Read<
+   93.61%     0.48%  rust-bitcoin-in  rust-bitcoin-indexer  [.] serde_json::read::next_or_eof                          
+   93.19%     0.60%  rust-bitcoin-in  rust-bitcoin-indexer  [.] std::io::read_one_byte                                 
+   92.55%     0.14%  rust-bitcoin-in  rust-bitcoin-indexer  [.] std::io::impls::<impl std::io::Read for &'a mut R>::rea
+   92.43%     1.50%  rust-bitcoin-in  rust-bitcoin-indexer  [.] <hyper::client::response::Response as std::io::Read>::r
+   91.04%     0.27%  rust-bitcoin-in  rust-bitcoin-indexer  [.] <hyper::http::h1::Http11Message as std::io::Read>::read
+   90.72%     2.96%  rust-bitcoin-in  rust-bitcoin-indexer  [.] <hyper::http::h1::HttpReader<R> as std::io::Read>::read
-   86.85%    82.85%  rust-bitcoin-in  rust-bitcoin-indexer  [.] log::__log                                             
   - 80.92% <serde_json::read::IoRead<R> as serde_json::read::Read<'de>>::parse_str                                     
      + serde_json::read::next_or_eof                                                                                   
   + 4.00% log::__log                                                                                                   
   + 1.91% jsonrpc::client::Client::send_request                                                                        
+    2.44%     0.02%  rust-bitcoin-in  rust-bitcoin-indexer  [.] rust_bitcoin_indexer::db::parse_node_block             
+    2.20%     0.00%  rust-bitcoin-in  rust-bitcoin-indexer  [.] jsonrpc::client::Client::send_request                  
+    2.18%     0.00%  rust-bitcoin-in  rust-bitcoin-indexer  [.] serde::de::impls::<impl serde::de::Deserialize<'de> for
+    2.18%     0.00%  rust-bitcoin-in  rust-bitcoin-indexer  [.] <&'a mut serde_json::de::Deserializer<R> as serde::de::
+    2.17%     0.00%  rust-bitcoin-in  rust-bitcoin-indexer  [.] serde_json::de::from_reader                            
+    2.01%     1.91%  rust-bitcoin-in  rust-bitcoin-indexer  [.] log::logger                                            
+    1.71%     0.43%  rust-bitcoin-in  rust-bitcoin-indexer  [.] <env_logger::Logger as log::Log>::log                  
+    1.59%     0.13%  rust-bitcoin-in  rust-bitcoin-indexer  [.] core::fmt::write                                       
+    1.44%     1.21%  rust-bitcoin-in  rust-bitcoin-indexer  [.] bitcoin::util::misc::he                     

As you can see, just because serde_json::read::Read has some trace! in parse_str somewhere, is completely destroying my program, for which I've enabled debugging just for my main module.

Is there anything that can be done about it?

dpc avatar Jan 04 '19 06:01 dpc

Does adding a filter to exclude logs from serde_json improve the situation at all?

As far as I know, serde_json::read::Read takes 1 byte at a time, so it's a pretty hot path. It probably shouldn't need to be logging at all. I'll have a look at what it's up to.

KodrAus avatar Jan 04 '19 22:01 KodrAus

So I looked at serde_json and it doesn't look like it does any logging.

KodrAus avatar Jan 04 '19 23:01 KodrAus

Not sure what exactly happened there: might have been something to do with optimalization causing this to point to slightly different function, but I don't think it's very relevant. I've hit this issue before, and it looks it's caused by env_logger matching being slow, so any library that has trace! or something in a hot loop, will start to hog the cpu.

I was using a very simple RUST_LOG=rust_bicoin_indexer which is a name of my root crate. My root crate prints maybe one line every couple of seconds. But my CPU usage went through the roof. Now I wonder if it is going to match only the root module, or does it have to go through the full body search or something.

In

https://github.com/sebasmagri/env_logger/blob/master/src/filter/mod.rs#L164

as soon as the target is enabled, every logging statement will be run against a regex, and there's a to_string() that will allocate a string per every filter string (at very least I'd move it out of the loop). To prevent that allocation from happening at all, env_logger could use a per-thread String buffer to reuse the allocation across many calls.

Now I've noticed that I can actually disable regex through feature, so I'll try it later. But in my case, I was hopping only a match against target/module will be made. And I don't understand why would make such a huge difference.

Maybe there's more of clever things that can be tried there to optimize the performance, or something is wrong somewhere.

dpc avatar Jan 04 '19 23:01 dpc

Ah right, that code hasn't been touched in a couple of years so there's probably plenty of opportunity to optimise it.

We can work on building up a decent set of benchmarks to track changes in performance.

KodrAus avatar Jan 05 '19 02:01 KodrAus

If somebody would like to work on env_logger performance I'd be happy to help them get started!

KodrAus avatar Jan 06 '19 21:01 KodrAus

I profiled my (rather large) backend server and to my surprise found that the time spent logging is orders of magnitude more than the actual program. I saw that slog has something where the printing is offloaded to a separate thread.

What would be your advice on this issue? Would switching to slog give any major improvements?

PvdBerg1998 avatar Jan 28 '19 21:01 PvdBerg1998

@PvdBerg1998 slog-envlogger is a fork of env_logger so it's filtering performance is going to be similiar, but other than this slog is supposed to be very fast, especially with slog-async employed to offload the serialization and io to another thread.

dpc avatar Jan 28 '19 21:01 dpc

Yeh, on the printing side env_logger doesn't do any batching so each log statement results in a line printed to the output stream. It's not particularly fast.

KodrAus avatar Jan 28 '19 21:01 KodrAus

Is there anything we can/want to do on our side? Not sure if something like offloading the work is in-scope of the crate to be honest. I'm happy about speed improvements however, and I'll probably also take a closer look at the matching algorithm

mainrs avatar Jul 22 '20 21:07 mainrs

IMO. env_logger (and generally the whole log ecosystem) should become more modular and env_logger itself should be split into a logical part (decide if something should get logged or not, based on env variables etc. and pass it to the "inner loger") and the formatting and outputting part. They could be combined together to give a backward compatible API.

I think my main problem was here was that each logging statement has to go through a regex (if regex feature is enabled). Maybe some tiny cache could provide a substantial speedup. Or maybe there's something else that can be done to avoid it.

dpc avatar Jul 22 '20 22:07 dpc

It would be nice to pull the filters out into a separate library, since they are pretty useful on their own, and for other loggers.

KodrAus avatar Jul 23 '20 02:07 KodrAus