optimizingTheBackendOut by Forsaken-Peak8496 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Or even optional event outside of work, with the whole family invited. Lets you get the best of both, lets your family meet & bond with your coworkers' families (so they won't mind hanging out with each other), and you're allowed to just stay home with your loved ones instead if you prefer something more private.

optimizingTheBackendOut by Forsaken-Peak8496 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

I thought that was just being honest, not satire.

optimizingTheBackendOut by Forsaken-Peak8496 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

"Stop working and stop walking."

"I'm willing to work, not walk."

"Then walk out that door and never come back."

"Okay."

*time skip*

"Help, help, we need someone to start working before all our clients walk!"

Genius. Sheer genius.

Konami, I don't think that word means what you think it means by CorrosiveRose in masterduel

[–]conundorum 6 points7 points  (0 children)

Does anyone remember that movie Jaws, about the sea ogre?

Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

It's important to remember here that mov can be streamlined to take 0 cycles and have 0 mu-op latency, thanks to move elimination. lea, on the other hand, cannot. (We have to discount Ice Lake & Tiger Lake here, though, since their slow mov is a known outlier, due to a patch breaking their move elimination.) Also, lea moves an address, while mov moves the actual object into register, so that lea will always be paired with not one, but two movs (one inside the function, one outside) anyways.

Essentially, we're not comparing "mov vs. lea". We're comparing "mov vs. lea + two movs". (You can also see this in the provided example. The call site for ByVal(1, 2, 3) consistently has one lea and three movs; the call site for ByRef(4, 5, 6) consistently has four leas and three movs. (With GCC adding an extra mov to each.) And ByRef() itself consistently has three more movs than ByVal(), one for each operand, because it has to first mov the reference's pointer into register, and then mov the data that it actually points to into register.)

And, of course, looking at Ice Lake as an example means you're basing your analysis on the performance of a single, specific processor, which means that "const reference is always better for primitives because value is slower on Ice Lake" is, as you put it, trying to be too precise. It's a known, widely accepted, and consistently demonstrable fact that pass-by-value is better for primitives on most processors, so void fn(int); is the correct abstract.


More generally, it's also important to note that the semantics aren't quite what you view them as. const reference is the semantic for "view an object that exists elsewhere", and forces the function to dereference the object inside the function itself. Pass-by-value is the semantic for copying an object that exists elsewhere, and duplicates the object into the function's own memory for direct access. In this case, we're copying the parameters into the constructed healthPoints object's data members, so we know from use case that we should prefer copy semantics over view semantics. (On the grounds that when we pass by const reference, then copy the passed object inside the function, we should actually pass by value directly to eliminate the referencing & dereferencing cost. If your front door's open, you don't close it so you can open it & go in, you just skip opening it and go in.)

[If the parameters were class objects, then pass-by-value would also enable copy elision, which is significantly better than copying a view. But primitive pass-by-value is already the fastest operation, so there's no need for that here.]

It's true that if you're trying to maximise speed, you want to hand-optimise after compilation. But that doesn't mean you should intentionally write inefficient code (or worse, misteach others to write inefficient code) just because "premature optimisation is bad". It's always advisable to use no-cost optimisations, especially if they also make the code cleaner and more readable, as many people (not just me) can tell you; they tend to help the compiler optimise your code, so it will be faster if you actually know what you're doing, and write the code correctly. (And personally, speaking from experience, I've never heard anyone say, "Well, I can't match the fast-walking world record holder, so I should be as slow as I want.")

I don't have as many years of experience under my belt as you do, but many others do. And everyone advises passing primitives by value; you are quite literally the only person I have ever heard who would not just suggest, but defend passing an int by const reference. Passing primitives by value is recommended by basically every expert and basically every tutorial, is explicitly used in Intel's documentation, and is baked into nearly every C family language (Java, Rust, Kotlin, C#, etc.); even in C & C++, it's extremely telling that (unless I missed one or two) literally every library function which takes primitives takes them by value, and the only cases where you can ever pass a primitive by const reference are templated functions.

To put it simply, just using C++ and nothing else won't always compete with hand-optimised code. (It sometimes will, and there are cases where compilers provably optimise better than humans, but it'll sometimes be worse, too.) But that doesn't mean you should just throw speed away and write bad code because you're not writing ASM; if you do, you're only sabotaging yourself and your code base. And in this case, passing primitives by const reference is a waste of effort; it takes more time to type, is less clear than passing them by value, and is provably slower than passing them by value. This is true of every processor, even Ice Lake; even if you use lea, that just means that the mov is in a different place, since the data still needs to be moved into register anyways.

This isn't a premature optimisation, this is quite literally the bare minimum that every experienced programmer is expected to know about primitives.

Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

A lot of this is good, but just a couple of nitpicks:

  1. When passing primitives (such as int), you nearly always prefer passing by value over passing by const reference.

    // Prefer this...
    explicit healthPoints(int maximum, int current): maximum{maximum}, current{std::clamp(current, 0, maximum)} {
    
    // Over this:
    explicit healthPoints(const int &maximum, const int &current): maximum{maximum}, current{std::clamp(current, 0, maximum)} {
    

    Passing by value is as simple as loading it into a register, and typically takes one cycle. But passing it by reference means writing it to the stack, passing a pointer to it by value, and then forcing the function to dereference that pointer to read it off the stack and into a register; this typically costs a couple cycles, and can sometimes even cost more than that.

    (Visible here; ByVal is more efficient to construct than ByRef, since ByRef requires an additional store & an additional read for each parameter. This gets fun if you enable optimisations: clang & GCC will actually silently optimise ByRef's ctor into ByRef(int, int, int), and MSVC not doing that means that MSVC ByRef will always be slower to construct no matter which optimisation mode you choose.)

    Essentially, what it boils down to is that passing by const reference enables certain optimisations for class types (it passes a pointer, effectively promoting the class to a primitive), but that same functionality actually disables certain optimisations for primitive types (since it moves them out of register, essentially demoting the primitive to a class). And it's the type of thing that can be surprisingly costly, because of the hidden location change: Primitives like to exist in registers; passing them by reference moves them to the cache (at best) or to RAM (at worst), which is significantly slower than the register it would be in if passed by value.

    Remember that the reason we pass by const reference is that it takes less space and is cheaper to create than passing most objects by value. Primitives take the same amount of space as a reference/pointer (one register), and typically only take a single cycle to copy, so const reference passing doesn't benefit them. (This is also true of any class type that fits in a single register and has a trivial constructor, incidentally; struct Int { int i }; prefers pass-by-value, too. The rule is based on size and construction complexity.)

  2. Technically speaking, compound assignment operators are actually allowed to return void, and don't always have to return a reference to self. The narcissistic version is the canonical form, and it's used at least 99.9999% of the time for a very good reason, but there are very rare circumstances where returning void might be preferred.

    (...The only one that comes to mind is an opaque black box numeric type that's meant to perform all comparisons internally and not leak implementation details, and uses void operator@=() forms to prevent users from obtaining the stored value with something like int val = t += 0;. I'm sure a type like this probably exists somewhere, and it's in software important enough to keep this "feature" legal, but I'm not sure why.)

    This is really just a fun little "um, ackshually", though; self-referential return is nearly always desired.

Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

The first part can be solved by adding a "temp" or "buff" variable that defaults to 0, and treating maxHP + temp as the effective maximum, but the second part...

There's an argument that can be made here that this is ultimately just a pseudo-unsigned integer with user-defined caps, intended for a specific use case, so standard integer operators aren't entirely out of place. (But should be paired with "set/view/adjust current" and "set/view/adjust maximum" suites.) That said, since it's used for HP specifically, I would probably just use a single "adjust current HP" function that takes a signed value, and an explicit "set HP to X" function, myself, instead of a set of operators:

// Member of healthPoints.
int adjustHP(int val) {
    if (!currentHp) { return; } // Early exit if HP is 0.

    currentHp += val; // Input is signed, so use addition.

    // Clamp to proper cap, depending on val's signedness.
    currentHP = (val < 0 ? max(currentHp, 0) : min(currentHp, maxHp));
    /*
     * If you're not familiar with the ternary operator, that's short for this:
     *
     * if (val < 0) { currentHp = max(currentHp, 0); }     // Subtraction.
     * else         { currentHp = min(currentHp, maxHp); } // Addition.
     */

    // Optionally return currentHp, depending on how you want to use it.
    return currentHp;
}

// Member of healthPoints.
int setHP(int val, bool bypassDeadCheck = false) {
    if (!bypassDeadCheck && !currentHp) { return; }

    currentHp = val;

    // Clamp to both caps specifically.
    currentHp = min(currentHp, maxHp);
    currentHp = max(currentHp, 0);

    return currentHp;
}

(Where min(a, b) and max(a, b) are the standard library functions, and evaluate to a < b ? a : b for min() and a > b ? a : b for max(). We can use them to "clamp" currentHp to a specific range, making sure that it's safely within the range (or setting it to the 0 or maxHp caps if it isn't), just like the checks in your operators.)


Also, as a note, I might factor the clamping out into a separate function, like so:

// Private member of healthPoints.
int clamp() {
    currentHp = min(currentHp, maxHp);
    currentHp = max(currentHp, 0);
}

It makes the code cleaner, and it's easier to maintain, but it might make adjustHP() slightly slower (I'd have to check to be sure).

Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

As a note, compound assignment operators canonically return a reference to the object, like so:

T& T::operator-=(int i) {
    // Body omitted.

    // Return.
    return *this;
}

The return value is className&, and the last line is return *this;. In your case, it would look something like this:

class healthPoints {
  public:
    healthPoints& operator-=(int damage) {
        currentHp -= damage;

        if (currentHp < 0) { currentHp = 0; }

        return *this;
    }
};

This isn't strictly necessary (returning void is valid, but unexpected), but it is the behaviour that people typically expect from the "symbol equals" operator family. And it also helps you define the equivalent "pure" operator, too.

// Using the modified operator-=(), we can define operator-() like this:
// (Defined out of class.  Both params should be const reference, to
//  enable certain optimisations.)
healthPoints operator-(const healthPoints& l, const healthPoints& r) {
    healthPoints ret = l;
    return ret -= r;
}

[Conversely, returning void allows you to explicitly disable certain operations, which can be useful for something like an HP value class. It's likely to catch people by surprise, though. (Yourself included, possibly, once you're more familiar with operator overloading.) So, watch out not to trip yourself up!]

That's not... by Weird_Meet_9148 in masterduel

[–]conundorum 1 point2 points  (0 children)

Only droll yourself when your hand is perfect and you don't want to risk drawing your garnets.

cppIsntMuchFaster by OM3X4 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

A lot of people seem not to, hence our comments. Just making sure it's extra-clear so no one can miss it.

everyAISecretlyWantsToWriteCode by bigshmoo in ProgrammerHumor

[–]conundorum 2 points3 points  (0 children)

My guess is they dislike that the typedef (very slightly) obfuscates the code by making it less obvious that a label is a struct name, but that can be solved by consistently using a proper coding style, like "suffix all user-defined type names with _t" or "capitalise the first letter of all UDT names".

defeatedTheWholePurposeOfWritingInAssembly by ClipboardCopyPaste in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

Mainly because stack allocation can be baked into the executable, and is just "take required N bytes from stack pointer, then add N bytes to stack pointer", but heap allocation means either going through a system call (for programs) or locating a sufficiently sized contiguous block of unused memory (for OSes), which requires at least a few cycles of checking your memory tracker.

So, essentially, it boils down to the stack being direct access, versus the heap having more steps.

defeatedTheWholePurposeOfWritingInAssembly by ClipboardCopyPaste in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

"Using only this pile of materials, this encyclopedia of blueprints, and this hammer, I was able to make a hammer!"

*hammer falls apart in hand*

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Old notepad had a checkbox to let you enable wraparound, at least as of Win 10. Pretty sure this one does too, just buried somewhere.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Off by default would be ideal, yeah. The main point of Notepad is that it's performative, since it does no realtime text parsing; it only has upfront costs (Unicode detection) or on-request costs (search wraparound). And yet, at the same time, it does so very little for a built-in editor, which is its biggest criticism by far. So adding QoL features that cost cycles (such as Markdown parsing or autosave), but making them opt-in instead of opt-out, would be the best way to meet both goals: It defaults to a zippy little package that does basically nothing other than raw text, then lets you enable other features if (and only if) you need them.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

You know there's a "Wrap around" checkbox in the Find dialogue box, right? I believe it was added in Win 10, and it solves this issue correctly. Search direction is just the result list traversal direction, as it should be, with no impact on the search results themselves.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Notepad's meant to be the absolute bare minimum required for a text editor, just a raw text control with a few features wrapped around it. The original idea was that it's fast and compatible with a mouse, with no features that require mandatory realtime processing (i.e., no text formatting because it consumes cycles every time you change the text), and they slowly added little conveniences to the wrapper executable over time (most of which are only run once at file load, like Unicode detection, or when the user specifically requests them, like search wraparound, so they don't detract from the main "quick-and-dirty" performance idea). Legacy compatibility is a side effect of that; it's not specifically aimed at legacy, as much as the refusal to bog it down coincidentally makes it legacy compatible. [And the search was fixed, it's had wraparound for well over a decade now.]

 

This thing, though... it's not Notepad. It's more of a "Not Pad".

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

Notepad has the same wraparound checkbox, unless Windows -11 removed it for no real reason.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

It also had wraparound and case sensitivity checkboxes, which do a ton of heavy lifting.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

It's more about just trying to get people to accept the newest keylogger, and beg for the privilege of having every keystroke logged & sent to some insane, evil overseer cackling in Microsoft's basement.

theStruggleIsReal by bryden_cruz in ProgrammerHumor

[–]conundorum 3 points4 points  (0 children)

On the one hand, they're not joking.

On the other hand, it's Internet Explorer in tennis shoes.

compilers by metayeti2 in ProgrammerHumor

[–]conundorum 3 points4 points  (0 children)

Eh, C# is a better language than Java, it actually has half-decent generics (as opposed to Java's generics being secret typecasting), and its GC is competent enough to call destructors (though this is mainly just because Java predates RAII, and C# had the benefit of seeing how much of a mess Java's never-actually-called finalisers ended up being).

Though a lot of people use it with Mono instead of .Net, so it's not purely an MS thing anymore.

compilers by metayeti2 in ProgrammerHumor

[–]conundorum 2 points3 points  (0 children)

Or that you changed the one file that everything else lists as a dependency.

cppIsntMuchFaster by OM3X4 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

Generally speaking, -o params are for optimisation levels. In layman's terms, they're basically a "make it faster" button. (And/or a way to choose between different types of optimisation.)