• LCrawford (unregistered)

    The frist developer was too lazy to recalibrate for the clock cycles required to run a loop, so they hard coded unrolled loops.

  • Jonathan (unregistered)

    On one C# system I worked on we had a requirement where entries posted into an accounting folio had to have their insertion order maintained which we achieved by recording a posted time and ordering the records on retrieval by it. In some cases though, multiple entries would be created at the same time and their DateTime.Ticks values were actually identical such that ordering by time would not guarantee displaying them in actual insertion order. We used an ORM, so the time recorded would happen in memory in the POCO before being "later" committed to a database in a unit of work.

    I considered putting in a property/ordering column, but there were potential race conditions, so I then considered doing a Thread.Sleep(1) which not only felt "not great", but thread scheduling meant that the delay would likely be quite a lot more than a millisecond, so I ultimately did something like the following:

    static void WaitAtLeastOneTick() { var currentTick = DateTime.Now.Ticks; while (DateTime.Now.Ticks == currentTick) { // NOP } }

    At first glance it seems like a bit of a WTF, but it solved the problem perfectly and the performance impact is likely negligible, if any at all.

  • Bob (unregistered)
    	CheKseg0CacheOn();
    	for (i=0;i<=SECONDS_1_U;i++) continue;
    	CheKseg0CacheOff();
    

    This disables an optimization on the microcontroller, then busy loops for what should be one second, and then enables that optimization again.

    You sure? Based on https://people.ece.cornell.edu/land/courses/ece4760/PIC32/Microchip_stuff/32-bit-Peripheral-Library-Guide.pdf it looks like it enables caching of program flash, then busy waits and then disables caching of program flash once again.

    Why you would want to do this is still unclear. I guess the delay loop has been calibrated with the caches enabled and they didn't want to re-calibrate it?

    But yeah, why not busy wait using a timer/counter if you really don't want to use interrupts for some reason.

  • dpm (unregistered)

    loop index initializes at zero, loop test is "less than or equal to" . . .

    Looks like an off-by-one to me.

  • Daniel Orner (github) in reply to Jonathan

    Maybe another option would have been to use a database trigger to set the time rather than setting it in memory within the transaction?

    Addendum 2022-07-12 09:23: Or maybe an autoincrement column?

  • Barry Margolin (github) in reply to dpm

    Maybe, or they calibrated it with this off-by-one taken into account.

  • Argle (unregistered)

    Eons ago, there was a joke program that had some message about flushing your disk drive. There was a pause, some sound effects that amounted to watery flushing sounds and it was done. Years and several hardware updates later, I found this old app again and re-ran it for the sake of nostalgia. But clearly it was designed to operate on a CPU clocked at 4.77MHz. After a second and some squeaks and buzzes and it was done. So disappointing.

  • Yikes (unregistered)

    I'd be surprised, if by engineered design they were supposed to wait for exactly 3 seconds (or as close as possible with the clock granularity), but I could be missing something... I'm also not sure why you'd want to always turn off the performance-enhancing program cache without checking to see that it was off before you started - to me that's TRWTF.

  • define (unregistered)

    Ah, the wisdom of "No magic numbers!!!1!!12!" in all its glory.

    You can't use numbers, not anywhere in the program. Instead, you must #define SECONDS_1_U 100 #define SECONDS_2_U 200 #define SECONDS_3_U 300 ..... #define SECONDS_79_U 7900

    What a gawdawful thing to do, on full display, right here, in this code example. It's abundantly obvious that the person who implemented this code went, "No 'magic numbers'? Ok. Fine." and didn't use even one single multiply-by-3 (or multiply by 249). That would be a magic number.


    #define U_1 1 #define U_2 2 #define U_3 3

    for (int i = 0, i < U_3 * SECONDS_1_U; i++) continue;

    I mean, it's so logical and immediately clear what this does! You'd have to be a blathering idiot to not see it!

  • Foo AKA Fooo (unregistered) in reply to Jonathan

    What if several threads do this waiting at the same time? They'll both continue the next ms and get the same time. Seems you got your race conditions just there.

    An ordering column, OTOH, if properly implemented, wouldn't. Even if an autoincrement column wasn't available for some strange reason, just use an atomic counter (if C# support that, otherwise a counter protected by a mutex), and you get distinct orders without caring about timing.

    Though "actual insertion order" gets a bit fuzzy on these time scales. Assume two requests arrive via the same network port in some order. This order may already depend on the inner workings of your network switch or router. Then the network stack/web server/dispatcher/whatever passes them to two threads, but the order the threads are scheduled to run and get to the point where they generate the ID (whether time or counter) may get switched around. And if the requests arrive on different network interfaces, it may already be hard to define which one was first.

    So it really only makes sense to talk about actual insertion order when they come from the same source (e.g. a batch job which sends many requests in short order). The proper solution then, of course, would be to number them at the source.

  • Spencer (unregistered) in reply to Bob

    I've written code for embedded systems that had no interrupts other than the system clock (It was a home built RTOS with no preemption). We had calibrated stuff like this based on exactly how long instructions took to run given our clock frequency.

  • The Thnikkaman (unregistered) in reply to Bob

    Especially amusing because PIC32 (Or specifically, the MIPS32 spec) actually gives you a lovely always freerunning timer called the Core Timer that's perfect for this exact use case (a blocking delay), and you don't need to use any timer modules or interrupts. Just need to know the number of Core Timer counts per ms/us which is just a function of the system clock (The Core Timer counts every other clock cycle of the system clock) and you can calculate by macro.

    Even if you weren't using the Core Timer though there's still definitely a few different and better ways to do this that...aren't this.

  • MaxiTB (unregistered)

    One thing I instantly noticed: The iteration starts with 0, so this actually means it should by i < SECOND_1_U. Obviously a mistake like that only happens to a very rookie developer, any dev with a bit experience wouldn't be that sloppy stylistically even tho it makes little difference in behavior.

  • mihi (unregistered)

    From the introduction, I was expecting the WTF being SECONDS_1_U * 3 causing an integer overflow/wraparound. Yet still, having two nested loops would be better than unrolling the outer one.

  • WatersOfOblivion (unregistered)

    And better hope nobody turns on optimizations. Any sane optimizer would eliminate the busy wait.

  • Andrew Klossner (unregistered) in reply to Bob

    "Why you would want to do this is still unclear. I guess the delay loop has been calibrated with the caches enabled and they didn't want to re-calibrate it?"

    Turning on the cache keeps the processor off the buses while it waits?

  • xtal256 (unregistered) in reply to define

    No I think the idea is that SECONDS_1_U is defined as the number of iterations that correspond to 1 second at the frequency of the CPU (on microcontrollers this is usually fixed). So it's more like #define SECONDS_1_U 5342342. In this case that kind of makes sense.

  • AussieSusan (unregistered)

    In most of the compilers I know for microcontrollers, any level of optimisation (including none for some of them), would have the compiler replace all of the for-loop with nothing. This is one of the most common faults I see on the microcontroller forums I follow. I agree that there are a multitude of better ways to program in delays from uSec to hours/days. Susan

  • WTFGuy (unregistered)

    @Foo AKA Fooo

    'Zactly. If you have exactly one atomically lockable source of these updates, number them there. Otherwise you must number them for order at the actual database commit point by the actual database itself. Anything else has ordering failures.

  • Your Name (unregistered) in reply to Argle

    Wasser in Laufwerk A:

  • (nodebb) in reply to AussieSusan

    The compiler being "smart" is why, in more professional code, the busy loop is directly in assembler. It's also usually only intended for short delays; longer delays (such as for a whole second!) are best done with hardware support; microcontrollers typically have good hardware for things like this, but a minimum useful timer granularity.

    I've got something a bit like this in Java code, but that's again because I needed a really short delay (for throttling UDP message sending, to stop the other receiver from overloading), shorter than anything that the system provided. There's a nanosecond argument to one of the standard sleep calls, but it's a total lie under the covers; busy looping works better (throwing a Thread.yield() inside stops the optimiser).

  • Daniel (unregistered) in reply to Bob

    There is a problem when you run code from an external flash that is also used as a storage location: You want to use caching for read access (to speed up code execution) but you usually need to switch off caching when you write to the flash. (I had to fix an implementation of such a pattern recently...)

  • (nodebb) in reply to AussieSusan

    In most of the compilers I know for microcontrollers, any level of optimisation (including none for some of them), would have the compiler replace all of the for-loop with nothing

    That would be a bug in this case. The loop variable i is not limited in scope to the for loop and so each one must be, replaced with i = SECONDS_1_U + 1;

  • BestBuy.com (unregistered)

    For a period of time yesterday, I noticed a 5 second burn-CPU-thread loop on a large electronic retailer's web site on their search results page. Blocking the main javascript thread in the browser, causing an unresponsive page. Maybe their engineers get ideas from DailyWTF?

  • WatersOfOblivion (unregistered) in reply to Jeremy Pereira

    Eliminating the loop is not a bug. Yes, it gets incremented every iteration, but it's not used inside the loop body and therefore dead. Since it's a single variable used across multiple loops the compiler would have to be sophisticated enough to know it's dead other than the uses as an index, but that's not a particularly hard analysis.

  • Wizofaus (unregistered) in reply to mihi

    If it really were 3 whole seconds I'd certainly expect you'd need to loop a lot more than 2^32 times. And wouldn't a compiler be free to optimize away the entire loop (unless the counter was declared volatile)?

  • (nodebb) in reply to Spencer

    We had calibrated stuff like this based on exactly how long instructions took to run given our clock frequency.

    I remember when I was first learning Z80 assembler (which I never used for anything of significance, but I did disassemble a bunch of programs to see how they did stuff, so it was fun nonetheless), one of the routines in the book I was reading was for a delay routine, which similarly took careful account of the exact clock cycles consumed by the routine overhead itself (including the call and return) to produce a delay of exactly the requested interval. I suppose that sort of thing is a bit easier to do when you don't have to worry about pipelining and branch prediction and all the other fancy stuff, and every instruction occupies a fixed number of clock cycles!

  • (nodebb) in reply to Wizofaus

    If it really were 3 whole seconds I'd certainly expect you'd need to loop a lot more than 2^32 times.

    Flashbacks to the Turbo Pascal days, where a lot of previously working programs suddenly failed to start with a divide-by-zero error if you ran them on a PC whose clock frequency was 200 MHz or higher, thanks to the initialisation code for the Delay function in the CRT unit (this being the old days, all the unit's initialisation code ran if you used the unit, you didn't need to be using the Delay function specifically). I got bitten by that in my research, it was working fine before the Christmas break and then mysteriously broke when I went back to it in the new year... took me a bit to find out what was going on.

  • define (unregistered) in reply to xtal256

    Yes, it makes sense for 1 second. So, why doesn't this coder do 1_second * 3? Why, instead, do they copys-paste the wait block three times? There's no logical reason to do that. Even a dumb, newbie coder would go "x * 3".

    The answer is "No Magic Numbers. You can't use * 3, define it as a constant."

    Now reread the comment..

Leave a comment on “Busy Busy Busy”

Log In or post as a guest

Replying to comment #:

« Return to Article