It seems that you're using an outdated browser. Some things may not work as they should (or don't work at all).
We suggest you upgrade newer and better browser like: Chrome, Firefox, Internet Explorer or Opera

×
avatar
kohlrak: The kind of thing you can't rely on is the stuff that's not standardized, for example an "int" isn't actually universally a 32bit number. It used to be 16bits (now called "short (int)"), so the old test for negatives used to be >>15, which worked. >>31 would still work (since this standardizes to "signed arithmetic right shift" or "sar") on most systems (since not all archs actually have signed right shift). However, it would break if "long long int" became the standard "int" (which is unlikely thanks to x86's "64bit" instruction encodings actually being limited to 40 bits).
Having int not be a standard size seems to be C/C++ specific.

In Java, the size of int is always 32-bits.

In Rust, there is no type called "int"; instead, you have types like i32, which is always a signed 32-bit integer, or u128, which is an unsigned 128-bit integer.

In Python, integers can be arbitrarily large. Python 2 had a distinction between int (finite size) and long, but Python 3 no longer distinguishes those at the language level (though the internal representations may still differ).

In JavaScript and Lua (5.1, the version that LuaJIT is based off), there are no integer types; all numbers are floats (I believe 64-bit IEEE floating point is what's used). Many classic dialects of BASIC were like that, and even those with integer types often have variables be floats by default. (Excluding BASIC dialects that don't support floating point in the first place, of course.)

By the way, why has this topic been "low rated"?
avatar
kohlrak: ...
No argument from me there.

But also remember that "speed" is not the be-all and end-all of a program. Unless you're working on a dedicated software where every cycle counts, or for a competition or similar, sometimes it's better (for example for general code maintenance) to have a general "slower" but cleaner piece of code in a real software. If a fast function uses lots of gotos and ends up as awful spaghetti code that's difficult to modify later, then I'd rather have slower neater one.

If I see (n%2)==0 in code, I immediately think it's testing for integer parity, if I see n&=1 then I immediately think it's testing for the last bit. If this is part of a larger piece of code, then the first one makes it immediately clear for me what the author meant and why he added it, while with the later I might have to pause and think for a second before realizing why it's used.

Of course in this example it's as simple as adding a single comment:
// testing for parity and this is the fastest method
But in general it might not be the case. I've seen my share of convoluted code that might be faster but a nightmare to maintain. Maybe faster, but at what cost.
avatar
kohlrak: The kind of thing you can't rely on is the stuff that's not standardized, for example an "int" isn't actually universally a 32bit number. It used to be 16bits (now called "short (int)"), so the old test for negatives used to be >>15, which worked. >>31 would still work (since this standardizes to "signed arithmetic right shift" or "sar") on most systems (since not all archs actually have signed right shift). However, it would break if "long long int" became the standard "int" (which is unlikely thanks to x86's "64bit" instruction encodings actually being limited to 40 bits).
avatar
dtgreene: Having int not be a standard size seems to be C/C++ specific.

In Java, the size of int is always 32-bits.

In Rust, there is no type called "int"; instead, you have types like i32, which is always a signed 32-bit integer, or u128, which is an unsigned 128-bit integer.

In Python, integers can be arbitrarily large. Python 2 had a distinction between int (finite size) and long, but Python 3 no longer distinguishes those at the language level (though the internal representations may still differ).

In JavaScript and Lua (5.1, the version that LuaJIT is based off), there are no integer types; all numbers are floats (I believe 64-bit IEEE floating point is what's used). Many classic dialects of BASIC were like that, and even those with integer types often have variables be floats by default. (Excluding BASIC dialects that don't support floating point in the first place, of course.)
Maybe, but most of those target VMs as well, which is an important distinction. You'll find that sizes aren't specified where there are not int types (like BASIC). My point was that 32bit "int" isn't a reliable standard, which was my point. Whereas there are other standards that are reliable (like the one in my intended solution).
By the way, why has this topic been "low rated"?
Something gives me the impression that the topic isn't what's low rated, but someone's opinion of the person. His posts have been targeted the whole way through.
avatar
dtgreene: Having int not be a standard size seems to be C/C++ specific.

In Java, the size of int is always 32-bits.

In Rust, there is no type called "int"; instead, you have types like i32, which is always a signed 32-bit integer, or u128, which is an unsigned 128-bit integer.

In Python, integers can be arbitrarily large. Python 2 had a distinction between int (finite size) and long, but Python 3 no longer distinguishes those at the language level (though the internal representations may still differ).

In JavaScript and Lua (5.1, the version that LuaJIT is based off), there are no integer types; all numbers are floats (I believe 64-bit IEEE floating point is what's used). Many classic dialects of BASIC were like that, and even those with integer types often have variables be floats by default. (Excluding BASIC dialects that don't support floating point in the first place, of course.)
avatar
kohlrak: Maybe, but most of those target VMs as well, which is an important distinction. You'll find that sizes aren't specified where there are not int types (like BASIC). My point was that 32bit "int" isn't a reliable standard, which was my point. Whereas there are other standards that are reliable (like the one in my intended solution).
Well, C and C++ do have a standard header that declares types like uint32_t, which is useful if you need an integer of a specific size (in this case, an unsigned 32-bit integer).
http://en.cppreference.com/w/cpp/types/integer

Rust, which does not run in a VM (it runs on bare metal, and has even been used for bare-metal embedded development), uses types like that as the only integer types (u32 being the equivalent of C uint32_t).
avatar
kohlrak: ...
avatar
ZFR: No argument from me there.

But also remember that "speed" is not the be-all and end-all of a program. Unless you're working on a dedicated software where every cycle counts, or for a competition or similar, sometimes it's better (for example for general code maintenance) to have a general "slower" but cleaner piece of code in a real software. If a fast function uses lots of gotos and ends up as awful spaghetti code that's difficult to modify later, then I'd rather have slower neater one.
First thing i notice is that people equate speed with "readability issues." Usually this results from resorting to "tricks" to make them happen, like my pass by reference trick, which would've thrown most people off. And i understand the need to focus on readability over speed, but if there's a function that is used alot and isn't going to be maintained much, you should focus on speed and size. Pareto distribution: 20% of the code will be used 80% of the time, so if you want to control your CPU requirements, changes made to that 20% will affect 80% of your requirements.

Space optimization is another animal, but to have the greatest effect on that, what i see going wrong the most would not be the result of readability, but a lack of understanding of the tools, or the tools having design flaws. I'd have to look on a case by case basis to elaborate.

If I see (n%2)==0 in code, I immediately think it's testing for integer parity, if I see n&=1 then I immediately think it's testing for the last bit. If this is part of a larger piece of code, then the first one makes it immediately clear for me what the author meant and why he added it, while with the later I might have to pause and think for a second before realizing why it's used.
This is due to the lack of it's usage. Since i naturally test parity with &, when i see %2, my mind immediately goes through the division remainder first, then i remember why we're getting a remainder from division from 2, rather than dividing by 2, which is to check parity. My logic is simply "I use % when trying to narrow a number of choices not governed by powers of 2." Knowing the power of 2 thing, if i am designing something with borders that have to be "around" a number, rather than actually having to be exactly a number, i try to land it on a power of 2, just so i can throw it into the &, especially if it's something i need alot of (plotting random stars on a background, for example). Not going to save much if a complex RNG is involved, but if you're hand-coding a custom RNG since the randomness doesn't matter as long as the resultant pattern isn't too obvious, you can benefit alot (moreso from your custom RNG than your sneaky border trick).

Of course in this example it's as simple as adding a single comment:
// testing for parity and this is the fastest method
But in general it might not be the case. I've seen my share of convoluted code that might be faster but a nightmare to maintain. Maybe faster, but at what cost.
My experience with convoluted code isn't the result of optimization, but of people trying despirately to make things work within a certain time frame, then forgetting that they declared 3 variables that they aren't using anymore, deleted a conditional for debugging, then readded it, but didn't take the brackets away from the first time they removed the conditional, to all sorts of silly things. Usually optimization is unreadable because it comes with obscure practices like the "pass by reference" trick, or sticking the word "inline" in front of a function when half the people don't even know what that means.
avatar
kohlrak: Maybe, but most of those target VMs as well, which is an important distinction. You'll find that sizes aren't specified where there are not int types (like BASIC). My point was that 32bit "int" isn't a reliable standard, which was my point. Whereas there are other standards that are reliable (like the one in my intended solution).
avatar
dtgreene: Well, C and C++ do have a standard header that declares types like uint32_t, which is useful if you need an integer of a specific size (in this case, an unsigned 32-bit integer).
http://en.cppreference.com/w/cpp/types/integer

Rust, which does not run in a VM (it runs on bare metal, and has even been used for bare-metal embedded development), uses types like that as the only integer types (u32 being the equivalent of C uint32_t).
With C and C++, you do run into the risk though that the header isn't available. The library is not necessary for C/C++ compliance, which is important if you're working with microcontrollers like ATMega 328P-PU, CORTEX-M, etc. The standard library is nice, but you should always know how to live without it, just in case you're given a project where you have to.

Rust, i'm honestly not familiar with. Given that level of typing, you now have me interested enough to google it. I like low level things, and C/C++ is starting to gravitate way too far from the metal for my needs.
Post edited June 07, 2018 by kohlrak
avatar
kohlrak: My experience with convoluted code isn't the result of optimization, but of people trying despirately to make things work within a certain time frame, then forgetting that they declared 3 variables that they aren't using anymore, deleted a conditional for debugging, then readded it, but didn't take the brackets away from the first time they removed the conditional, to all sorts of silly things. Usually optimization is unreadable because it comes with obscure practices like the "pass by reference" trick, or sticking the word "inline" in front of a function when half the people don't even know what that means.
In Go, trying to compile a program with unused variables will result in a compiler error.

In Rust, doing this will result in a warning, and you also see that happening with many C and C++ compilers as well.

avatar
dtgreene: Well, C and C++ do have a standard header that declares types like uint32_t, which is useful if you need an integer of a specific size (in this case, an unsigned 32-bit integer).
http://en.cppreference.com/w/cpp/types/integer

Rust, which does not run in a VM (it runs on bare metal, and has even been used for bare-metal embedded development), uses types like that as the only integer types (u32 being the equivalent of C uint32_t).
avatar
kohlrak: With C and C++, you do run into the risk though that the header isn't available. The library is not necessary for C/C++ compliance, which is important if you're working with microcontrollers like ATMega 328P-PU, CORTEX-M, etc. The standard library is nice, but you should always know how to live without it, just in case you're given a project where you have to.

Rust, i'm honestly not familiar with. Given that level of typing, you now have me interested enough to google it. I like low level things, and C/C++ is starting to gravitate way too far from the metal for my needs.
In the case of uint32_t not being available, if the compiler you're using does support unsigned 32-bit integers, you can make your own typedef. uint32_t isn't some complex type; it's just a synonym for a basic type. It's a lot better than using int only to find out that ints are only 16-bit on the architecture you're targeting, and getting strange results.

Rust is known, in part, for its borrow checker; if you try to write code that, in C, would produce undefined behavior (for example, returning a reference to a temporary variable), the borrow checker will detect that and give you an error. If there's a situation where you actually need to do something that's not memory-safe (for example, you need to access a memory-mapped hardware register that's at a fixed address), there's always "unsafe", which lets you do things like dereference raw pointers.

I really do recommend checking out Rust; there's some nice resources out there, and you can even go to
play.rust-lang.org to test out little snippets or Rust code. Also, Rust's error messages are quite good; you can actually learn by trying to compile some code and following the error messages you get.
Post edited June 07, 2018 by dtgreene
avatar
kohlrak: My experience with convoluted code isn't the result of optimization, but of people trying despirately to make things work within a certain time frame, then forgetting that they declared 3 variables that they aren't using anymore, deleted a conditional for debugging, then readded it, but didn't take the brackets away from the first time they removed the conditional, to all sorts of silly things. Usually optimization is unreadable because it comes with obscure practices like the "pass by reference" trick, or sticking the word "inline" in front of a function when half the people don't even know what that means.
avatar
dtgreene: In Go, trying to compile a program with unused variables will result in a compiler error.

In Rust, doing this will result in a warning, and you also see that happening with many C and C++ compilers as well.
Yep, and why do they care to when the compiler optimizes those variables away? Because it has a huge impact on readability. The floating brackets thing would be a royal pain to pick up on, but not a silence-able warning about using bitwise operations where logical operations make more sense. Either way, other than the obfuscation contests, the most unreadable code I see is usually code where someone was desperately commits to shotgun debugging.

Then there's code that isn't really unreadable, but people have trouble reading it because the failure of formal education leads it to become Heavy Wizardry. You can identify the difference by the amount of confusion: shotgun debugging usually comes with alot more whitespace or unnecessary brackets (and not the type of brackets that aren't necessary, but still exist for readability), while heavy wizardry is something that looks just as readable as the rest of the code, but you just don't understand it. See also: Dunning-Kreuger effect.
avatar
kohlrak: With C and C++, you do run into the risk though that the header isn't available. The library is not necessary for C/C++ compliance, which is important if you're working with microcontrollers like ATMega 328P-PU, CORTEX-M, etc. The standard library is nice, but you should always know how to live without it, just in case you're given a project where you have to.

Rust, i'm honestly not familiar with. Given that level of typing, you now have me interested enough to google it. I like low level things, and C/C++ is starting to gravitate way too far from the metal for my needs.
In the case of uint32_t not being available, if the compiler you're using does support unsigned 32-bit integers, you can make your own typedef. uint32_t isn't some complex type; it's just a synonym for a basic type. It's a lot better than using int only to find out that ints are only 16-bit on the architecture you're targeting, and getting strange results.
Right, which isn't a problem. The rule of thumb is to know your target platform and don't automatically assume your code will be platform independent, simply because it was not written in assembly. Meanwhile, you should know which tricks are likely to be platform independent.
Rust is known, in part, for its borrow checker; if you try to write code that, in C, would produce undefined behavior (for example, returning a reference to a temporary variable), the borrow checker will detect that and give you an error. If there's a situation where you actually need to do something that's not memory-safe (for example, you need to access a memory-mapped hardware register that's at a fixed address), there's always "unsafe", which lets you do things like dereference raw pointers.

I really do recommend checking out Rust; there's some nice resources out there, and you can even go to
play.rust-lang.org to test out little snippets or Rust code. Also, Rust's error messages are quite good; you can actually learn by trying to compile some code and following the error messages you get.
I like that. One thing about assembly is that it is wordy, but it's really nice having explicit control over your code when you need it. And with C/C++, it offers some nice protections, but i distinctly remember having trouble in C++ with the socks API, because it insisted that i would move the data from one variable to another in a loop. I tried to simplify this, knowing that the data was contiguous in memory, by casting the pointers as uint32, but the compiler was not having that. I remember spending like a week on it, then rage quitting, and writing it (he whole program) in a few hours in 32bit x86 assembly, which I understand alot of people have a hard time wrapping their heads around.