- Differences between C and C++ - 8 Updates
- Intricate loop - 3 Updates
- ostringstream performance - 6 Updates
- Which newsreader (was Re: A plea...) - 1 Update
- Multithreaded Programming : Always use spinlocks for performance? - 2 Updates
- About my posts - 2 Updates
- "Carbon, a new programming language from Google, aims to be C++ successor" - 1 Update
- Main thread distributes workload to worker threads, then they all rendezvous later - 2 Updates
| Juha Nieminen <nospam@thanks.invalid>: Jul 25 01:14PM Some people often object if someone conflates C and C++, as if C++ were just a pure superset of C, pointing out that they are, in fact, different languages and, in some aspects, actually incompatible with each other. That got me thinking: What are all the differences between the two languages, in behavior and meaning, when it comes to their common syntax? In other words, I'm not here talking about different keywords and different syntax that compiles in one but not the other. I'm talking about code that does compile as both C and C++, but will be interpreted or behave differently depending on which (and this makes them incompatible). Here are some of the things that come to mind. What other things are there? 1) A 'const' variable at the global scope will have external linkage by default in C (unless explicitly made to have internal linkage with 'static'), but internal linkage by default in C++ (unless explicitly made to have external linkage with 'extern'). 2) The type of 'A' is int in C, but char in C++. 3) This one is really obscure: In C, this: int f(int (*)(), double (*)[3]); int f(int (*)(char *), double (*)[]); is a valid function declaration, and equivalent to: int f(int (*)(char *), double (*)[3]); (This one is so obscure that I'm certain the vast majority of C programmers, even experienced ones, would be surprised it even compiles. However, the example is directly from the C standard, so it's pretty legit.) In C++ the two first lines declare two different functions, taking different types of parameter. Calling one is not the same thing as calling the other. |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Jul 25 04:18PM +0200 On 25 Jul 2022 15:14, Juha Nieminen wrote: > example is directly from the C standard, so it's pretty legit.) > In C++ the two first lines declare two different functions, taking different > types of parameter. Calling one is not the same thing as calling the other. Yes, `void` argument list needed to say "no arguments" in C. I guess the most crucial thing is that type punning via union is well-defined in C, but UB in C++. - Alf |
| Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 06:47PM +0300 25.07.2022 16:14 Juha Nieminen kirjutas: > syntax that compiles in one but not the other. I'm talking about code that > does compile as both C and C++, but will be interpreted or behave > differently depending on which (and this makes them incompatible). This little program outputs 0 when compiled as C, and 1 when compiled as C++, at least with gcc 10.2. I'm not quite sure if this is meant to be that way. #include <stdio.h> #include <stdlib.h> int main() { double x = -0.5; int y = (int) 2*abs(x); printf("%d\n", y); } |
| "Öö Tiib" <ootiib@hot.ee>: Jul 25 08:51AM -0700 On Monday, 25 July 2022 at 16:14:50 UTC+3, Juha Nieminen wrote: > example is directly from the C standard, so it's pretty legit.) > In C++ the two first lines declare two different functions, taking different > types of parameter. Calling one is not the same thing as calling the other. There are not too lot of little things in C that differ from C++ like that: * C++ has lot of keywords that C does not have or has as macro (bool) or typedef (wchar_t) * logical expressions like 1 == 2 result with int (not bool) * character literals like 'a' are of type int (not char) * differences with const * differences with inline * differences with static * C has VLAs * differences in enum type size requirements Those differences can cause same code to compile to different behaviour in C and C++ silently but it is usually obscure, specially constructed code. |
| Ben Bacarisse <ben.usenet@bsb.me.uk>: Jul 25 05:14PM +0100 > That got me thinking: What are all the differences between the two > languages, in behavior and meaning, when it comes to their common > syntax? Let's see... "abc" is of type char * in C and of type const char * in C++. Then there all the keyword differences. For example, in C, new is an ordinary ID and in C++ restrict is an ordinary ID. There are quite a few of these! The rules for compatible pointer types are stronger in C++. Basically, you can only add a top-level const when passing arguments in C. In C++ function declarations, () means (void), but in C it means an old-style function with unspecified arguments. In C, at file scope, int x[]; is a "tentative definition" (which will resolve to int x[] = {0}; if there are no further declarations of x) but in C++ it's just an error. In C, f( (int[]){1} ), calls f with a temporary array (it's a "compound literal"), but that's forbidden in C++. Some things that are compound literals in C /are/ valid in C++. For example: struct s { int v; }; ... f((struct s){1}); is ok in both, but add a pointer and you get the same difference as above: g(&(struct s){1}); // ok in C, not ok in C++ And now I'm out of time... > int f(int (*)(char *), double (*)[]); > is a valid function declaration, and equivalent to: > int f(int (*)(char *), double (*)[3]); Yes. This is C's rules for "composite types" in action. > In C++ the two first lines declare two different functions, taking different > types of parameter. Calling one is not the same thing as calling the > other. However, you can, in fact call the second f with a double (*)[] argument because of C++'s rules about similar types (at least I think those are the rules that apply here). -- Ben. |
| Muttley@dastardlyhq.com: Jul 25 04:19PM On Mon, 25 Jul 2022 17:14:05 +0100 >is ok in both, but add a pointer and you get the same difference as >above: > g(&(struct s){1}); // ok in C, not ok in C++ IOW mostly contorted syntax that was probably never intended to be used but happens to be legal due to the way the C parser works. |
| Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Jul 25 05:45PM +0100 On Mon, 25 Jul 2022 13:14:35 -0000 (UTC) > example is directly from the C standard, so it's pretty legit.) > In C++ the two first lines declare two different functions, taking different > types of parameter. Calling one is not the same thing as calling the other. One thing I don't think mentioned so far, for those who like low-level twiddling of trivial types: in C, except for bit-fields all objects are composed of contiguous sequences of one or more bytes, which thereby comprise an array of bytes in C. In C++ trivially copyable or standard layout types (ie C-like types) are required to be comprised of contiguous bytes, but these do not comprise an "array" of bytes. Hence you can use pointer arithmetic to access and/or modify the bytes of object entities in C, and this is a fairly common practice for some low-level work, but not in C++ (save that in C++, when within a C-like entity you can at least successively increment a byte pointer by one because in C++ every object, including a byte, can be treated as an array of size 1). One other related point is that in C a pointer to any narrow character type is exempt from the strict aliasing rules, whereas in C++ pointers to signed char are excluded from the exemption. |
| Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 07:49PM +0300 25.07.2022 18:47 Paavo Helde kirjutas: > int y = (int) 2*abs(x); > printf("%d\n", y); > } This example can be actually made a bit simpler, messing with ints is actually not needed. I think this is now similar to what I stomped on myself in real code. #include <stdio.h> #include <stdlib.h> int main() { double x = -0.5; double y = 2.0 * abs(x); printf("%g\n", y); } |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Jul 25 02:48PM +0200 This code parses an unsigned decimal integer spec with possible group delimiters. Since it's intricate it can probably be very much simplified? Result_uint result = 0; bool overflowed = false; char ch; // Declared here to be accessible to the loop's update expression. Prev_char previous_ch = '\0'; for( p_end = p_first; p_end != p_beyond; previous_ch = ch, ++p_end ) { ch = *p_end; WITH_CONST( ch ) { if constexpr( group_delimiters_allowed ) { if( ch == group_delimiter ) { if( previous_ch == group_delimiter ) { return Ret( no_result, Error::consecutive_group_delimiters ); } continue; } } if( not( ch %in% ascii::digits ) ) { return result; } const Result_uint before = result; result = 10*result + (ch - '0'); if( result < before ) { overflowed = true; } } } ... where `WITH_CONST` just adds `const`-ness to the specified name so that it's provably not modified in that code block, and where the `Prev_char` type by default is `char`, but can be dummy (optimized away operations) when code is instantiated to parse without group delimiters. I declared `ch` before the loop so it can be accessed by the `for` loop head's update expression, so that that expression can handle the `continue` statement. I guess if one can get rid of the `continue` then one can move the declaration of `ch` inside the loop and just say directly that it's `const`? - Alf |
| Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 04:25PM +0300 25.07.2022 15:48 Alf P. Steinbach kirjutas: > I guess if one can get rid of the `continue` then one can move the > declaration of `ch` inside the loop and just say directly that it's > `const`? I see your obscure WITH_CONST macro and counter with my obscure UPDATE_AND_CONTINUE macro: #define UPDATE_AND_CONTINUE(a,b) {a = b; continue;} std::string_view input {p_first, p_beyond-p_first}; Prev_char previous_ch = '\0'; for(const char ch: input) { if (/*...*/) { if (/*....*/) { // ... UPDATE_AND_CONTINUE(previous_ch, ch); } } // ... UPDATE_AND_CONTINUE(previous_ch, ch); } Who says my macro is uglier than yours? |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Jul 25 04:01PM +0200 On 25 Jul 2022 15:25, Paavo Helde wrote: > UPDATE_AND_CONTINUE(previous_ch, ch); > } > Who says my macro is uglier than yours? Heh :-), it's certainly less reusable functionality, but it's an idea. And I like fresh ideas. Just a shame that C++ doesn't yet formally support push and pop of macros so that one can freely use local macros... <url: https://docs.microsoft.com/en-us/cpp/preprocessor/push-macro?view=msvc-170> <url: https://gcc.gnu.org/onlinedocs/gcc/Push_002fPop-Macro-Pragmas.html> I would also have liked language support for `WITH_CONST`. There's the whole $ space available for new keywords. It could be `$with_const`. Re the `string_view`: that might seem smart but part of the parsing effect is to produce a final `p_end`, beyond the number spec, where the caller can continue higher level parsing (if any). That's also the reason for the boolean `overflow`. To move the `p_end` beyond the spec even if overflow. Thanks, - Alf |
| Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 12:20PM +0200 I tried to evaluate the performance of ostringstream with MSVC, clang++-12 under Linux and g++. #include <iostream> #include <sstream> #include <vector> #include <chrono> #include <iomanip> #include <array> using namespace std; using namespace chrono; int main() { constexpr size_t N = 1'000'000; auto bench = []<typename StringGen>( StringGen stringGen ) -> double requires requires( StringGen stringGen ) { { stringGen( (size_t)123 ) } -> std::same_as<string>; } { vector<string> vs; vs.reserve( N ); auto start = high_resolution_clock::now(); for( size_t i = N; i--; ) vs.emplace_back( stringGen( i ) ); return (double)(int64_t)duration_cast<nanoseconds>( high_resolution_clock::now() - start ).count() / N; }; ostringstream oss; auto genOss = [&]( size_t x ) -> string { oss.str( "" ); oss << setw( 8 ) << setfill( '0' ) << hex << x; return oss.str(); }; cout << bench( genOss ) << endl; auto genManual = []( size_t x ) -> string { using arr_t = array<char, 8>; using arr_it = typename arr_t::iterator; array<char, 8> str; arr_it it = str.end(); while( x && it > str.begin() ) *--it = (x % 10) + '0', x /= 10; for( ; it > str.begin(); *--it = '0' ); return string( str.begin(), str.end() ); }; cout << bench( genManual ) << endl; } The above program takes about 300ns for each ostringstream-generated string on my TR3990X under Windows with MSVC 2022, but only 17ns for my own generator; it's nearly the same with clang-cl 13 under Windows. Unter Ubuntu with g++ 11 on a 13 yr old Phenom it's abotu 110 and 36. clang++-12 under Linux (different standard libary than under Windows) takes 125 vs. 43ns on the same computer. I won't expect that a stream is that fast like my hand-optimized code, but I think that this could go even faster with ostringstream under Linux, and Windows for sure. |
| "Öö Tiib" <ootiib@hot.ee>: Jul 25 03:51AM -0700 On Monday, 25 July 2022 at 13:20:09 UTC+3, Bonita Montero wrote: > I tried to evaluate the performance of ostringstream with MSVC, > clang++-12 under Linux and g++. ... > oss << setw( 8 ) << setfill( '0' ) << hex << x; ... > --it = (x % 10) + '0', x /= 10; ... Output of those programs is likely different? One looks hex ... other decimal. A function hard-coded for narrow case compared with widely dynamically configurable one (with all those setw, setfill and imbue) will likely win forever. For fairness compare with std::to_chars() that has narrowed down the spec of configurability to be easier to make efficient. |
| Wuns Haerst <Wuns.Haerst@wurstfabrik.at>: Jul 25 01:36PM +0200 Am 25.07.2022 um 12:51 schrieb Öö Tiib: >> --it = (x % 10) + '0', x /= 10; > ... > Output of those programs is likely different? One looks hex ... other decimal. OOOOOOOOOOh, you're right, hex would be even faster. And with decimal I won't get all digits in the 8 characters. |
| Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 01:42PM +0200 Am 25.07.2022 um 12:51 schrieb Öö Tiib: > configurable one (with all those setw, setfill and imbue) will likely win forever. > For fairness compare with std::to_chars() that has narrowed down the > spec of configurability to be easier to make efficient. It's obvious that this is faster, but I didn't expect the streams to be so slow. auto genManual = []( size_t x ) -> string { using arr_t = array<char, 8>; using arr_it = typename arr_t::iterator; array<char, 8> str; arr_it it = str.end(); static char const digits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; while( x && it > str.begin() ) *--it = digits[x % 16], x /= 16; for( ; it > str.begin(); *--it = '0' ); return string( str.begin(), str.end() ); }; This is the right function but it isn't faster although there are no slow /10-divisions. |
| Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 03:53PM +0300 25.07.2022 13:20 Bonita Montero kirjutas: > I tried to evaluate the performance of ostringstream with MSVC, > clang++-12 under Linux and g++. [...] > I won't expect that a stream is that fast like my hand-optimized code, > but I think that this could go even faster with ostringstream under > Linux, and Windows for sure. We have covered this before. C++ streams are slow because of * lots of flexibility, i.e. * lots of virtual function calls * lots of dynamic allocations * locale support * perform both formatting and file output By using a stringstream you have avoided the file output part and potentially also the locale support part (not sure about that, maybe on Linux?). The virtual calls and memory allocations are still there. In other words, you are using a trained carpenter with a chisel for preparing firewood, of course it will be slow. If you are concerned about performance, you should start with std::to_chars() as others have already commented. |
| Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 03:12PM +0200 Am 25.07.2022 um 14:53 schrieb Paavo Helde: > * lots of dynamic allocations > * locale support > * perform both formatting and file output Except from the virtual functin calls this might hurt, but that's still a metter of implementation. The formatting-part wouldn't be much different than I do that. |
| Vir Campestris <vir.campestris@invalid.invalid>: Jul 25 12:11PM +0100 On 19/07/2022 19:50, Jack Lemmon wrote: > Perhaps you should post this to Thunderbird Newsgroup where they might > tell you what your mistakes. Thunderbird is pretty robust newsreader. I did. I even raised a formal bug, and found other people reporting the same thing. Sadly the Thunderbird team seem more interested in new features. I might get around to downloading the source and investigating, but despite having just retired I don't seem to have the time. Andy |
| "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jul 24 04:55PM -0700 On 7/24/2022 1:20 PM, Chris M. Thomasson wrote: > a single-producer/multi-consumer queue. If you are interested in sheer > performance, there are several interesting options. Perhaps I can help > you. Do you care if you get FIFO, or LIFO? Oh. Reading your original thread. |
| "Öö Tiib" <ootiib@hot.ee>: Jul 25 03:21AM -0700 On Sunday, 24 July 2022 at 23:17:44 UTC+3, Chris M. Thomasson wrote: > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > Yikes! You really should have a backoff in there. Do you want your > worker threads to be running full speed with absolutely nothing to do? Yes. Whatever spinlock should be held for rather short time. Here we are misnaming infinitely busy poll loop (that is terrible anti-pattern) as "spinlock". The symptoms from OP that "CPU fan getting a bit load and the OS getting a big laggy" are because one of cores is full busy warming room. It is visible in whatever activity monitor or task manager of operating system and anyone half-savvy suggests to kill and uninstall it right away. |
| Juha Nieminen <nospam@thanks.invalid>: Jul 25 05:47AM > Don't bother, i have just posted 3 posts and i will stop it and i will > not post for a long time You have told that lie in the past many times. |
| Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 09:04AM +0200 Am 25.07.2022 um 00:57 schrieb Amine Moulay Ramdane: You have a bipolar disorder, i.e. you're manic-depressive. You could get a much better life on medication. |
| Juha Nieminen <nospam@thanks.invalid>: Jul 25 05:44AM > - Community that works to be welcoming, inclusive, and friendly Especially "inclusive" is a warning flag, because in modern political vocabulary it means pretty much the exact opposite. |
| "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jul 24 05:04PM -0700 On 7/22/2022 7:28 AM, Frederick Virchanza Gotham wrote: > I have one main thread and eight worker threads. > The main thread is reading in data from a file, and at every 8 kilobytes, it distributes the workload evenly to the eight worker threads, so each worker thread processes 1 kB at a time. > By the time all the worker threads have finished processing their kilobyte, the main thread has prepared another 8 kB workload to distribute. It depends on how flexible you want to be. Can worker threads begin to process an 1kb chunk of data _before_ the main thread is finished getting the 8kb chunk ready, so to speak? Is the 8kb important, or arbitrary? > It is very important that the previous workload has been fully processed before the next workload is dished out. So, the main thread cannot read the next 8kb and prepare it for work _until_ the previous 8kb has been processed? [...] |
| "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jul 24 05:33PM -0700 On 7/22/2022 7:28 AM, Frederick Virchanza Gotham wrote: > // same workload. This won't happen if each thread takes > // milliseconds to execute, and if the thread scheduler takes > // only microseconds to start another thread going again. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ YIKES! This sounds very fishy. > { > std::this_thread::sleep_for(std::chrono::milliseconds(1u)); > } ^^^^^^^^^^^^^^^^^^^^^^^^ Strange. > sem.acquire(); > bitmask_started |= (1u << thread_id); > assert( 0u == (bitmask_finished & (1u << thread_id)) ); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment