|
Is there any widely-used and ergonomic facility for zero-copy views into const strings? I feel like https://en.cppreference.com/w/cpp/string/basic_string_view is what I'm reaching for, but it looks like I'd need to touch raw pointers by hand to split a string. I have an application that's spending most of its time reading in input lines from a textual tabular format, but the actual useful work is pretty fast. It's not blocked on disk I/O, and right now it's using getline to read whole lines in, passing a pointer to that line through a queue to a pool of worker threads, each of which is wrapping a complete line in an istringstream and reading from it with >> in a loop, which is slow as balls. The profiler shows that 90% of total CPU time is spent in basic_istream::sentry::sentry, meaning this approach is really expensive. As a first shot I'm just going to do boost::algorithm::split and split into a vector of strings, but there's got to be a better way to do this. A regex iterator would also copy, and I'm just reading from a const stream, there must be a zero-copy way to do this that doesn't involve falling back to manually walking it and saving a collection of pointers. Even if I give up and roll this C-style, I couldn't use strtok if I wanted to because there's between 8 and 64 threads doing this work at any given time, and strtok isn't thread safe. Edit: This is basically what I'm wanting to do: https://techoverflow.net/2017/01/23/zero-copy-in-place-string-splitting-in-c/. Edit again: ohhhh this looks right up my alley: https://www.boost.org/doc/libs/1_70_0/libs/tokenizer/doc/tokenizer.htm I swear, I am unable to find solutions until after I've made a dumb post about it. Twerk from Home fucked around with this message at 04:30 on Jul 17, 2022 |
# ? Jul 17, 2022 04:19 |
|
|
# ? Jun 1, 2024 12:40 |
|
Twerk from Home posted:Is there any widely-used and ergonomic facility for zero-copy views into const strings?
|
# ? Jul 17, 2022 04:27 |
|
Twerk from Home posted:Is there any widely-used and ergonomic facility for zero-copy views into const strings? I feel like https://en.cppreference.com/w/cpp/string/basic_string_view is what I'm reaching for, but it looks like I'd need to touch raw pointers by hand to split a string. I have an application that's spending most of its time reading in input lines from a textual tabular format, but the actual useful work is pretty fast. It's not blocked on disk I/O, and right now it's using getline to read whole lines in, passing a pointer to that line through a queue to a pool of worker threads, each of which is wrapping a complete line in an istringstream and reading from it with >> in a loop, which is slow as balls. strtok_r is thread safe if you really want to use it. I have not found anything in the standard for dealing with strings reasonably. Almost all of my string data is super simple so I don't have to worry about crazy unicode stuff so I'd probably try to do this: Instead of splitting into small entries, read big chunks of the file and pass the entire thing (build w/ a stringstream, make a string, should std::move? not super efficient, but easy) to a processor to handle it in full instead of splitting it into smaller pieces. Makes it easier to manage the data, once you use a string view you have to track ownership of the original piece of data, so by moving big pieces you have to manage less ownership. if you are iterating the string to read chunks (at least N bytes, end on newline or something) you could also note the delimiters as you go and pass those alongside the data. How fast does it need to be? I might mmap the file so I never have to copy things and I can simply make string_views into the mmap'd file if possible. Create a thread that does nothing but split the mmap'd region into views of views (columns/lines?) and pass on the chunks to workers.
|
# ? Jul 17, 2022 14:53 |
|
Twerk from Home posted:Is there any widely-used and ergonomic facility for zero-copy views into const strings? I feel like https://en.cppreference.com/w/cpp/string/basic_string_view is what I'm reaching for, but it looks like I'd need to touch raw pointers by hand to split a string. I have an application that's spending most of its time reading in input lines from a textual tabular format, but the actual useful work is pretty fast. It's not blocked on disk I/O, and right now it's using getline to read whole lines in, passing a pointer to that line through a queue to a pool of worker threads, each of which is wrapping a complete line in an istringstream and reading from it with >> in a loop, which is slow as balls. Just two notes on this: small strings don't allocate memory, so depending on your data you might be fine either way. If you need to send the substrings into API that needs zero termination, you are doomed to a copy anyway.
|
# ? Jul 18, 2022 11:32 |
|
Foxfire_ posted:Headers specific to separately compiled libraries whose binaries end up in /usr/local/lib (the ones that are under libs/LibraryName/src/, not boost/include/LibraryName/ in the boost source code) use normal quotes since if there's a local vs system conflict while building that library, they want the local file. Like if you put a file named cpuid.hpp in /usr/local/include/, compiling Boost's Atomic library still wants its own file, not that one I am gonna start here. If Boost.Atomic wants its own cpuid.hpp file, then it should place it somewhere like boost/atomic/detail/cpuid.hpp, and include it as #include %boost/atomic/detail/cpuid.hpp%, not cross its fingers, hope for the best, and use file-location-relative include path, because code:
Using the full in-project path, so you do #include "boost/atomic/detail/cpuid.hpp" at least removes the "welp, I moved the including file and now I picked up completely different header" failure mode. Due to the rewriting behaviour, you can still run into issues if you installed an old version of your library, and then start moving around headers in your checkout, but there isn't really a way to guard against that except keeping your devenv clean. Foxfire_ posted:Using from-the-base-of-your-project paths seems like a reasonable goal to me, but it'd be better implemented by sticking to the normal <> vs "" convention and modifying the user include path to include the root of the project instead of modifying the system include path (-iquote on gcc vs -I). One extra stat doesn't seem likely to actually matter to compile time enough to be worth doing something unusual. Tricks with iquote don't work on MSVC or nvcc (at least I haven't seen iquote equivalent in nvcc's docs) and this is what the docs say for MSVC "" includes quote:If the #include directive is specified using double-quote form, it first searches local directories. The search begins in the same directory as the file that contains the #include directive. If it fails to find the file, it searches next in the directories of the currently opened include files, in the reverse order in which they were opened. so if you use "" you end up searching loving everywhere
|
# ? Jul 18, 2022 14:32 |
|
Xarn posted:Just two notes on this: Thanks to everyone who threw in suggestions, it was extremely helpful. I'm still doing copying as I am parsing the lines into my own data structure, but swapping out istringstream with operator>> for boost::tokenizer resulted in runtime by wall clock being 1/10th what it was before, with CPU time being 1/20th. 20x performance increase! The worker threadpool now can be much smaller, or even just one thread, which is going to let me now accept other types of files more easily with a single thread filling a different, more complicated data structure that would be harder to share between threads. I'm sure that the mmap'd file could make this thing really rip and get me another 4x performance, but if I mmaped the file then it'd be more complicated to read in compressed input, and I'd be having to copy out uncompressed lines somewhere anyway. Here's an article I stumbled on mentioning how optimized Perl can be for some plain file reading / string parsing situations and comparing it to mmap-ing: https://yongweiwu.wordpress.com/2017/09/14/c-cxx-performance-mmap-and-string_view/ Next issue on the plate for me is that this application and several others my group works with are and have been using unsynchronized bools as flags for communications between threads. I'm pretty sure that these need to be atomic<bool> , or protected with mutexes or something but it's been working like this for years. I assume we've just gotten lucky with undefined behavior so far? Also, I'm glad I had a reference book around, because my Java-brained rear end was about to slap "volatile" on there, but it turns out that volatile does not create memory fences in C++. Twerk from Home fucked around with this message at 19:21 on Jul 18, 2022 |
# ? Jul 18, 2022 19:14 |
|
Twerk from Home posted:
It does on MSVC, but uh, go use atomics/mutexes so it doesn't explode in your face later. Also resist the siren call of non seqcst atomics. The perf is not worth it
|
# ? Jul 18, 2022 19:26 |
|
The non-atomic bools might be working because they are write-once? It's not a bad thing if it is. That is, you are not implementing critical sections with it, because the lack of pipeline flushes and compiler barrier will gently caress that up.
Beef fucked around with this message at 20:56 on Jul 18, 2022 |
# ? Jul 18, 2022 20:49 |
|
I use quotes for headers that were written by us, and angle brackets for anything out of my control. You know, the way God intended.
|
# ? Jul 19, 2022 00:20 |
|
Presto posted:I use quotes for headers that were written by us, and angle brackets for anything out of my control.
|
# ? Jul 19, 2022 00:51 |
|
If God was responsible for C++, I'd be ignoring his intentions for good reason.
|
# ? Jul 19, 2022 02:08 |
|
C++ is definitely absent of God.
|
# ? Jul 19, 2022 02:12 |
|
Absurd Alhazred posted:C++ is definitely absent of God. God codes in Lisp.
|
# ? Jul 19, 2022 06:51 |
|
qsvui posted:If God was responsible for C++, I'd be ignoring his intentions for good reason.
|
# ? Jul 19, 2022 10:29 |
|
Xarn posted:Also resist the siren call of non seqcst atomics. The perf is not worth it I agree that trying to use relaxed is rarely a good idea, but acquire/release are fine in basically every situation where atomics work at all. The way I always think about it is that there are a lot of circumstances where lock-free atomics aren’t good enough to get the semantics you want, and a tiny fraction of them happen to be fixable by sequential consistency. rjmccall fucked around with this message at 17:27 on Jul 19, 2022 |
# ? Jul 19, 2022 17:24 |
|
Don't non-SeqCst atomics make a bigger difference on ARM than they do on x86? My rough understanding is that all atomics compile down to the equivalent of SeqCst on x86, so looser atomics only affect how the compiler is allowed to optimize, but on ARM there's separate instructions with weaker guarantees that are potentially faster for weaker atomics
|
# ? Jul 19, 2022 20:35 |
|
You actually do need slightly stronger operations to do seq_cst on x86 because the architecture allows loads to be reordered with stores. The usual approach is to do seq_cst stores with XCHG because that’s semantically also a load and therefore cannot be reordered with other loads. On ARM, it’s complicated. ARM64 atomics are naturally seq_cst. ARM32 needs explicit fences to do seq_cst, IIRC.
|
# ? Jul 19, 2022 20:54 |
|
Beef posted:The non-atomic bools might be working because they are write-once? It's not a bad thing if it is. That is, you are not implementing critical sections with it, because the lack of pipeline flushes and compiler barrier will gently caress that up. They're being used to tell threads that work is done, in a really hacky way. Multiple threads are reading from a queue, and then when all work has been submitted to the queue then the queue empties, the publisher thread sets a bool* to true that all of the threads are seeing and using as a signal to stop polling the queue. The threads are polling in a loop, and then sleeping if the queue was empty and it didn't get work. I'm guessing a better approach would be actually using condition_variable on the lock with wait_for, but then I'm still going to need to have it periodically checking the flag to see if it should be shut down so in the real world it's not much different, other than a thread getting notified when something new came through the queue, so it wakes up and starts sooner than the current sleep-based waiting. Edit: Actually, I guess I could get rid of timers entirely by having all the threads wait() on the lock protecting the queue, and then notify_one when I put something into the queue, then when it's time to shut everything down set the flag then notify_all. Twerk from Home fucked around with this message at 21:54 on Jul 19, 2022 |
# ? Jul 19, 2022 21:32 |
If you need to notify of/wait for job completion, a std::promise might be the most appropriate way of expressing that.
|
|
# ? Jul 19, 2022 22:05 |
|
Not to mention lldb is a huge buggy piece of poo poo. I use an old macbook for game dev and I’m thinking of switching to gcc just so I can maybe have better luck with gdb
|
# ? Jul 20, 2022 02:49 |
|
You can use gdb with clang and lldb with gcc.
|
# ? Jul 20, 2022 03:01 |
|
Plorkyeran posted:You can use gdb with clang and lldb with gcc. Oh really? For some reason I’ve had a lot of trouble getting gdb to work on my clang builds on mac
|
# ? Jul 20, 2022 03:32 |
|
rjmccall posted:
I agree, but we are talking about a person coming from Java, who just now figured out that volatile doesn't mean atomic. Chances of them happily applying acquire-release properly is low, and would get them bogged down in something likely doesn't matter for them.
|
# ? Jul 20, 2022 15:44 |
|
nielsm posted:If you need to notify of/wait for job completion, a std::promise might be the most appropriate way of expressing that. Yeah, to translate from Java, I'm really missing having a ThreadPoolExecutor that manages its own queue of incoming tasks, can be .shutdown() into a state that allows it to complete all of the tasks currently in the queue, and returns promises. It looks like Folly has some pretty nice ThreadPoolExecutors available: https://github.com/facebook/folly/blob/main/folly/docs/Executors.md and I also see that Boost has a thread_pool: https://www.boost.org/doc/libs/1_79_0/doc/html/boost_asio/reference/thread_pool.html. That's certainly the way we'll go in the future, where appropriate. At a glance, boost_asio is header-only, which means it'd be a pretty easy dependency to introduce. What this application has right now is a std::queue and worker threads polling it in a loop based on a timer, with a shared boolean controlling the loop, as I mentioned. I do want a way to make the queue have a fixed maximum size, to provide backpressure so that if worker threads are not keeping up with the input thread, it will slow adding to the queue. If I do a relatively straightforward bounded blocking queue type solution like this one, then I'd need some way to interrupt threads that are blocked waiting for an item to be added to the queue when it's time to shut down. It looks like Boost has a solution for that as well, but Boost.Thread is new to me and we're just using std::thread right now. I'm also not 100% confident that I understand the docs fully. It looks to me like boost::thread::interrupt will interrupt threads that are wait-ing with a boost::thread_interrupted exception, but not threads that are currently doing work, which is pretty cool. That makes it sound like when all work had been submitted to the queue and the queue was empty, then I could call .interrupt() on all of the threads and the next time they hit the wait() in the loop, an exception would be thrown.
|
# ? Jul 20, 2022 21:04 |
|
I don't like the boost thing because: (A) throwing exceptions for normal control flow are almost never a good idea for understandability and (B) it will be throwing from any of boost interruption point that happens to be in the middle of the work being done, not just if a thread was waiting on the queue (unless you jump through more hoops to suppress that for all the other worker code) It should be straightforward to modify your simple synchronized queue with: - A shutdown() operation that sets a flag (inside the mutex) and then notify_all()'s the condition variable to wake up anyone who was sleeping on it - Change the dequing operations to: A) On entry, test if the shutdown flag is set and return a "Queue is shutdown" indicator instead of sleeping B) Once they're asleep, change the condition being waited on so that instead of waiting for size > 0, they wait for (size > 0) || shutdown and then returns the popped item / "Queue is shutdown" accordingly
|
# ? Jul 21, 2022 02:45 |
|
My last post about includes was controversial, but can we at least agree that using "" for external and system headers should be criminal?
|
# ? Jul 23, 2022 17:44 |
|
Xarn posted:My last post about includes was controversial, but can we at least agree that using "" for external and system headers should be criminal?
|
# ? Jul 25, 2022 04:43 |
|
What is the scope of declarations within the initializer of a for loop? Does it vary across different implementations or standards? I've previously run into compilers freaking out about multiple declarations from the following: code:
code:
edit: Of course I could just try and see what happens with a given compiler but I'm mainly wondering about the recommended/most portable way is BattleMaster fucked around with this message at 20:11 on Jul 27, 2022 |
# ? Jul 27, 2022 20:07 |
|
BattleMaster posted:What is the scope of declarations within the initializer of a for loop? Does it vary across different implementations or standards? Have to do this in the language version I'm using. code:
|
# ? Jul 27, 2022 20:11 |
|
BattleMaster posted:What is the scope of declarations within the initializer of a for loop? Does it vary across different implementations or standards? According to the Cpp Reference for for: quote:
There are probably versions of C++, C, Javascript, etc, that do this differently, but here at least it seems like you shouldn't have had issues just having two for loops with the same initialized variable internally.
|
# ? Jul 27, 2022 20:16 |
|
Not quoting standard here, but I was under the impression C's for initialization is in the enclosing scope and C++'s is in its own.
|
# ? Jul 27, 2022 21:42 |
|
Hmmm... puttingC++ code:
|
# ? Jul 27, 2022 21:51 |
|
C89 didn't allow declaring a new variable in a for loop control part. It was a common compiler extension to allow that, but whether it is scoped to the inside of the loop or not was inconsistent between different compilers language extensions. GCC will refuse it entirely if you pass --std=c89
|
# ? Jul 27, 2022 22:04 |
|
Probably not this because you’re getting a redefinition error, but in C89 you can’t declare the variable inside the for (), you must use a previously declared variable. e: took too long.
|
# ? Jul 27, 2022 22:06 |
|
Thanks, everyone. So the way I'm already doing it seems like the way to do it then.
|
# ? Jul 27, 2022 22:06 |
|
Does anyone in here have advice on packages for making simple GUIs? I know the usual advice is to write your GUI in another language even if your backend is C++, but what I want is so simple that it feels like it really ought to be easier to not add another language to the project. All I really need is things like radio select buttons, checkboxes, text input, file select prompts--basically just a visual wrapper for the same options present on the command line interface. It needs to be open source (or at least usable in an open source project). Cross-platform would be nice but I'll take what I can get--windows is the minimum. Every package I've found seems to be insanely heavy-weight, intended for people writing extremely complex software way beyond the capacity I actually need, with complexity and difficulty to link/include commensurate with their power
|
# ? Jul 27, 2022 22:09 |
|
BattleMaster posted:Thanks, everyone. So the way I'm already doing it seems like the way to do it then.
|
# ? Jul 27, 2022 22:27 |
|
Foxfire_ posted:C89 didn't allow declaring a new variable in a for loop control part. It was a common compiler extension to allow that, but whether it is scoped to the inside of the loop or not was inconsistent between different compilers language extensions. GCC will refuse it entirely if you pass --std=c89 Uh... well, anyway, play around with Godbolt until you get the right complaint, I guess. cheetah7071 posted:Does anyone in here have advice on packages for making simple GUIs? I know the usual advice is to write your GUI in another language even if your backend is C++, but what I want is so simple that it feels like it really ought to be easier to not add another language to the project. All I really need is things like radio select buttons, checkboxes, text input, file select prompts--basically just a visual wrapper for the same options present on the command line interface. It needs to be open source (or at least usable in an open source project). Cross-platform would be nice but I'll take what I can get--windows is the minimum. I really liked working with ImGui. It's immediate mode, so you have to call it every frame. The benefit is that you don't have to do a lot of setting up to do things, you just do them as you need them.
|
# ? Jul 27, 2022 22:43 |
|
Seconding imgui. I no longer dread doing UI stuff for tools.
|
# ? Jul 27, 2022 23:10 |
|
|
# ? Jun 1, 2024 12:40 |
|
Showing my own ignorance here, but is it really necessary to be backed by a proper rendering engine like directx or opengl if all I want is buttons and file select prompts and stuff? I just sort of assumed that that wouldn't be necessary, but maybe that's wrong because every library I've looked at has been a generalizing abstraction over rendering engines.
|
# ? Jul 27, 2022 23:12 |