2011-04-09

What happens if...

Back again. This time I fished out something I had put apart for future fun. It is simply a piece of code that, like a lot of pieces of code I've seen recently, drove me crazy; since it is clearly wrong, but people using it think it is right, just because it happens that it works. (Moreover names chosen for the functions are misleading, but this is a different fact).

How about that, you ask: if something happens to work, it means it is right. Doesn't it? Wrong. Of course, it does not work miraculously: it works since the "real world space" of inputs which feeds it, is smaller than the "theoretical space" of inputs that the code pretends to handle.

Less words, let's look at the code (pseudocode).


procedure Copychar (start, end : integer,
src, out dest, repl : string);

var tmp : string,
i, k : integer;

k := 0;
for i := 0 to src.length;
if i >= start-1 and i < repl.length then
tmp[k] := src[i];
k := k + 1;
else if i >= end then
tmp[k] := src[i-1];
k := k + 1;
end if;
end for;
dest := tmp;
end procedure.


Hopefully every real programmer (being using Pascal or whatever) can see how ill this code is (and the procedure name does not explain what the code's intentions were). First, the for runs from the first character (indexed by 0) to past the last one (indexed by the length of the string minus 1); the guard ring is on the length of the repl string, not on src that is the variable indexed by i. If the repl string is longer than src, bad things may happen.

So the procedure must have at least this constraint: repl.length <= src.length; with this (undeclared) constraint, the index i can index src correctly in the first if. When the index has passed that "threshold" the else if is taken into account for sure, and its body executed when i >= end.

This code is so evil that it is hard to explain why (and it is hard to imagine that it did the job it was written for! But if I would show the rest of the code, you will see that this is very specialized code, camouflaged as generic function, used in a very "special" way, in a very "special" context).

The intention of the procedure (by the name and by input parameters) seemed to be to substitute a string with another, where of the old string we know just where it starts, and where it ends. But if we take a look at it, we see it does not read from repl, and it does indexing in the wrong way...

A correct, correctly general procedure could be instead (I am using more abstractness in place of explicit for loops):


procedure ReplaceStrPart (start, end : integer,
src, out dest, repl : string);

dest := "";
dest := dest + src[0:start-1];
dest := dest + repl;
dest := dest + src[end:];

end procedure.


Hopefully everyone who can be considered a programmer can translate this with for loops and indexes, if the language does not support strings slicing or similar syntax (note: in the syntax a[i:f], if i > f, nothing is copied, while a[i:i] is the same as a[i] and picks the i-th character).

This procedure does what we expect and has a name that is a little bit better.

As for the original evil code, the programmer failed to write a senseful generic function/procedure; rather, he proceeded in a trial-error-fix fashion, in a "limited input space", and he had to "compensate" for what likely he recognized as odd behaviour, until he reached the purpose... I would say, by chance. And everything went fine since the "input space" stood limited, though (I know this) there was no reason why it should happen, there was no a special constraint or handshake about that... it simply was so since people have their habits and generated strings keep their "places"...

2011-02-01

February

Turned my head just to see the path and woops, it's already February. As usual, since January had to end. Oh, happy new year to everybody!

I'm thinking about some serious project to feed my mind. Currently I am trying to let my artistic fantasy fly, and technical or sub-technical stuffs are a little bit behind. Indeed they are stacking into my mind, one day they will pop, hopefully not too late.

I am sorry there's no a January post, this is my only current concern! :)

2010-12-12

Acer again

At last I've bought it: a new Acer Extensa 5235... Once I said I won't buy an Acer again because it was unsupportive with GNU/Linux and too much sort of "Microsoft is the best, buy it with my computers", but it happened this one came without Microsoft Windows... Maybe (surely) not the best hardware I could get, but it is a lot more than I've got before, and cheaper than I planned at the beginning.

It came with "Linpus", that I wiped out after discovering a part of it (if not the whole system) was compiled in 2008, and that it was badly installed (in fact the computer started only selecting "start with the last working config" from grub menu), and that it wasted a lot of hd giving not too much... thus I've rejected the dual boot idea and reinstalled Ubuntu 10.10, and I am still "tuning" it (again:().

Having this powerful hardware, and a lot of room on the hd (going from 30 Gbyte shared among MS Windows and GNU/Linux to 250 Gbyte made me ask myself what I can do with all that free space...), I've installed many things I had not installed before, e.g. and notably FlightGear.

While installing software and software, I've realized I have badly partitioned the hd, and soon I'll have to change something; anyway this should not be an issue I hope.

Leaving some notes that could be useful:


  • To be able to control brightness from Ubuntu, I had to add acpi_osi="Linux" into GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub (you have to escape the " and write indeed \"), and so in the same time I become aware of the fact that grub is changed: no more menu.lst, it's built on the run now...

  • If you install from "third parties" or, even worse, from files that are not .deb (like the Adobe Acrobat Reader which give an executable) and you are not able to edit your menus anymore, you could find interesting to know that the program for menu editing is called alacarte (from french à la carte, I suppose) and if you run it from the command line you'll be informed of what went bad; in my case, it happened that some directories and files in my ~/.config/menus folder had root as owner, instead of me... of course, a consequence of a badly written installer called with sudo; solved with sudo chown -R ME:ME .config/menus; check for symlinks before.

  • Webcam and microphone works without any effort, and wireless too...

  • ZynAddSubFx had problems and I've uninstalled it; though it is an interesting piece of software, I am now "devoted" to Csound and other tools, so I don't need it; however I think the knot that made it not work could be back when running other apps. I hope I am wrong.

  • Emacs started with a too high window (frame), I had to apply a fix in the .emacs; currently I've used a solution found in a blog, but I do not like it since it change the size after it was opened, so the user can see the change; however the solution tries to get the best size at "runtime"

  • Though this notebook was not sold with MS Windows, the keyboard has its logo on it... as usual, I've mapped it to Meta (mainly for Emacs usage); and of course the Menu key is bound to Compose

  • Euro and Dollar key near the "direction keys" do not work; xev does not "listen" to them; I will find a solution one day; but I find those keys mostly unuseful, it would be more interesting to map them to something more interesting than €$, even if their position suggests the binding can't be "critic"



I haven't copied data from the old good hd into this one yet, so I feel still al little bit "naked". Since the old laptop is out of order, I'll need to put its hd in a IDE-usb "bay" or something like that. I'll do.

2010-12-08

Traffic stats

I've just seen that there are stats about blog traffic! And I've just discovered a lot of traffic came from StackOverflow, a place I've almost discontinued to frequent — good place for practical solutions to practical problems, but absolutely deaf and blind for reasoning, "theoretical" analysis of hypothesis, exploring knowledge and so on (like any other Q&A site, no matter how much technical they call themselves)...

Anyway since it can produce traffic to this very "useful" blog, the piece of me allured by net popularity (!) is suggesting me not to forget the place... Now that I am thinking about it, I've left alone a war-speech about the output of a (intentionally) wrong piece of C code and distinction between "undefined" and "unspecified" behaviour in the standard paper for the language. It's enough for today, but in the weekend, if I'll own a brand new laptop, I'll poke the fire until someone can explain for real and competently why that code outputs what many people expect, as if it would be "unspecified behaviour", and what should realistically happen under the cover of the compiler in order to manifest the "undefined behaviour" — my position was that the specific code will output always what indeed it outputs, despite the "undefinition", and so, in the specific case, for the specific code, no "undefined behaviour" can manifest itself (and indeed even the unspecified behaviour would be hidden by the way the code was written, it was about passing a "problematic" argument to a printf with a format string that would take only one argument, details on SO, but I'll be back on the topic, don't be afraid).

Wo, two posts in a day, I am growing the statistics!

How many days in a month?

A coworker proposed to me a little challenge sometimes I think about. My first solution, though correct, was rejected since it used the ternary operator ?: and there's a way to succeed without.

Today is holiday here in Italy and I am using my spare time pushing the button of my almost-dead laptop just to hear the one-long-beep-two-short-beeps sequence (hoping not to hear it), then hold the button to turn it off and then stick hitting keys on the job laptop — where I am now.

After I've read a lot of pages from On the Edge - The Spectacular Rise and Fall of Commodore, almost finishing it, I decided to let my eyes be enlightened by the LCD of the job laptop, where there's nothing more interesting to do than surfing the net and using gcc (MinGW) in the unpleasant environment MS Windows XP provides.

Ok stop making the post longer than it could be...

The challenge was: using just an expression, get the number of days in a specified month (given as a number from 1 to 12). No-one told me about constraints for February so I assumed it has 28 days; moreover, initially there was no prohibition of the ternary operator...

My first solution was:

g = 31 - ((m < 9)? (m+1)%2 : m%2) - 2*(m==2);

which worked on all the system I've tested it (MS Windows, Sun Solaris on SPARC, and GNU/Linux on Intel) but obviously contains a possible bug dued to m==2 that does not need to be evaluated as 1 when true, though likely it does (As std C says, boolean expressions evaluate to 1 when true; unless we're dealing with non std compliant compiler, or if we translate the idea in other languages).

My second solution is:

g = "103012310321"[m-1] - 18 - (m&2);

This one is maybe tricky but is more robust (provided m is in [1,12] and we are on a system using ASCII), and can be easily modified in order to yield 29 for February. Moreover, what it makes me like it is that it uses something rarely many non-expert programmers are aware of about C (I mean using "array notation" for a literal string). The practice could be marked as bad, but it is great when codegolfing!

About codegolfing... If I am not wrong, that code in GolfRun would be "103012310321"m(=18-m2&- but currently I can't test it since my already-compiled (for GNU/Linux) work-in-progress GolfRun interpreter is on the Real Out-Of-Work Laptop, though I could checkout the SVN repository on this machine and try to compile it with MinGW on MS Windows — what a pity it uses Gtk+ and GMP extensively and I want not to go into the trouble of having them properly installed (ready for development usage) on this hateable system.

The third solution to the challenge that comes into my mind is about looking at the bits. The previous solution already does it... a bit! But the idea can be extended; for this purpose I've made the following table


XY ABCD
31 1F 1 11 11 1 0001
28 1C 1 11 00 2 0010
31 1F 1 11 11 3 0011
30 1E 1 11 10 4 0100
31 1F 1 11 11 5 0101
30 1E 1 11 10 6 0110
31 1F 1 11 11 7 0111
31 1F 1 11 11 8 1000
30 1E 1 11 10 9 1001
31 1F 1 11 11 10 1010
30 1E 1 11 10 11 1011
31 1F 1 11 11 12 1100


We have four bits in input and we need to "produce" just 4 "patterns" (two bits) in output. The boolean table old school (and semplification thereafter) will give us a solution (paper and pen here for a while...) (^ stands for an overbar on the following letter, i.e. for the logical not):


^X = A + B + ^C + D
^Y = ^A^D(B + C) + AD^B



That gave me the code:


g = 28 |
((((((((~m)>>2)&((~m)<<1)&((~m)>>1))^1) &
(((~m)^2)&((~m)>>3))) |
((m>>3)&((~m)>>2)&m&1))^3)&3);


I am rather dissatisfied with this: surely it can be optimised someway, e.g. by doing simple observations like: no need to handle higher bit in logic since it needs just an exception (when m is 2); try to work in "positive logic"; mix ideas from the previous solution with the "gate logic" stuff... Anyway, for now it is enough, I have to go and do better things: I have a button I must push.

2010-12-04

Bye bye TM250

Maybe 1 post per month is a very low activity for a personal blog, should I be more chatty? Ok, let's say so: car broken, a lot of money to make it work again but this is not hard to handle. The thing that is hard to handle is to accept that my laptop is old, and it started to stop working. Well, not exactly. It's hard to accept the fact that it indeed works still well, except for a small detail that makes it unworking most of the time. If the diagnosis is right, it is about the BIOS EPROM... CPU is ok. Hard disk is ok (good! no data loss for now), LCD works, fan spins, some keys is a little bit deaf but not too much, and I have an external usb keyboard... RAM is ok (I went from 256M to 1G, remember?), gfx card is ok... everything's ok, but that little piece, which decided to fool me (if the diagnosis is right, again).

It happened one day when instead of seeing the Acer logo screen, and then the Grub boot loader, a long annoying beep followed by two short beeps greeted me... What's up? A lot of BIOS says graphics card problems this way. But a deeper seek made me informed about the fact that 1L-2S is (should be!) a problem in the flash of the BIOS... Likely.

Somewhere I've found a solution: reflashing the ROM, twice or three times to be "safer", and everything should go... Not so for me (or this was not the problem...?) When luckly the PC started, after 20 or so attempt, I tried the procedure. It seemed ok for a while: turned off, restarted (after minutes), and it was ok. Hurrah... but then I waited longer and the problem showed itself again.

This time it took longer before a turn-on attempt was successful. Reflashed (using provided software) the Phoenix BIOS 1.15, ten times or so, but likely this won't solve... Now I am using the laptop, since I only rebooted between MS Windows and Ubuntu, never turned it off (yet...). And it's all ok. I know tomorrow it won't boot, I will try and try, and at some point I'll give up. And I will decide to buy a brand new laptop... In italian there's the slang local verb "rosicare"; it should exist a synonym I can then translate into english, but in this moment it comes not into my mind... it is the feeling you can taste e.g. when you are so close to a goal and then, suddenly, a small stupid detail goes wrong and you lose the goal.

My pre-euro-era laptop still works... except for that stupid detail that if fixed would make this piece of hardware last for many years still ... one year would be enough for me!

Useful post, isn't it?

2010-11-13

Drop a drop

Finally my badly over-tweaked "old" Mandriva's got wiped out by a fresh brand new Ubuntu 10.10. With the advent of a bit more of memory (from 256M to 1G!) I've decided to try again a full featured modern desktop and accept all the comforts of the defaults, though a little bit too much windowsish and even though accepting defaults is usually a bad idea. There will be time to tweak this system too and make it usable just by me...

In the meantime I was disappointed by a really annoying bug I've noticed while watching a flash video on YT. At first the guilty, to me, was the flash player... But soon I've discovered it was not. Submitted a bug report for "choppy audio", ... but recently I've realized it was not about it. The bug is bigger: the whole system is stuck "occasionally".

Today, I've timed the choppiness and surprise, it's very regular: one "stuck" every ten seconds. I've searched the net simply with "ubuntu stuck every ten seconds" and found this link very useful. Then I tried the nomodeset and woah now it is ok. On an Ubuntu wiki I've found how to disable permanently it by adding a file in /etc/modprobe.d containing simply an option for the i915 driver: options i915 modeset=0. Indeed I've not restarted the system yet... however, if the boot option worked, the permanent solution should too.

This is however still an annoying bug. I don't know if KMS is really good, but its problem with Intel chipset should be fixed.

Another thing I did on Mandriva was to kill the tslash from the key t, and put in its place the þ. I don't know way in the "latin" layout (type) for danish, norwegian (etc) the þ is where it should be, while otherwise it is bound graphically (instead of phonetically) to the p key.

After reading here and there about how to use xmodmap, and the .Xmodmap file in the home, I've decided to fix it my way, as I did in Mandriva: editing the file /usr/share/X11/xkb/symbols/latin (maybe on Mandriva was located elsewhere, I can't remember). Did it and now that I've mapped (this time using the provided configuration tool) the Menu key to the Multi key and the unuseful Windows key to Meta key, I feel better.

The next step will be to fix a bit Nautilus: I don't like the Windows-like behaviour when you double-click on files it "does not know". I was used to be able to open all files (known or not) with a text editor (Emacs usually) or an hex editor (ghex2), but it seems that I have to pass to the "windows-steps" to do it: if a filetype is unknown, pick a program from a list, remember the association (no, never!) and of course if the file is "known", the context menu will show the item to open it with the program that "understands" it. It can be ok, but I want also always the items to open it with an editor (text and hex). How do I do it?

From the packages point of view, I was able to install everything I needed; in particular I was worried about interpreters/compilers I had installed on Mandriva. Luckly I was able to install maybe all what I had, from C (of course!) to Algol 68 Genie, with apt-get or compiling it (MMIX, MMIXX and Algol 68G only, currently, while the rest was all found in .debs in the repo or on the net). Rebol being proprietary is still not installed, but I will; and also fasm (flat assembler) is missing (but of course there's nasm).

2010-10-09

My own thoughts about software development methodologies

work in progress draft
and it always will be


I wish to scatter some thoughts about the subject. There's no a particular order, deep analysis or claim for correctness.


  • Defining the project and its requirements


    • Do not let the developers define the requirements! Requirements come first, then it comes developing code (with feedback, if needed/required).


  • Documenting APIs (if appropriate)


    • Be brief and effective, do not add unuseful redundancy; it must be techinical, even when it is a "high level" API

    • Let the dirty work be done automagically: force programmers to document into the code, using a special, fixed syntax (well-known or created ad hoc), but no literate programming — maybe with exceptions to be defined

    • APIs' docs must not expose internals; if you need internals, put them in a separate document

    • References and links, if appropriate; no copy-pasting

    • If there's a protocol to access the API, describe it in a formal way (if it is a standard, just reference the standard document describing it, avoid giving your own incomplete simplistic description); if needed, train whoever has to understand the formal description, rather than sticking to a informal loosy description — which however is suitable as introduction and to give a general picture, if appropriate

    • Do not use a format which is not mechanically parseable and that it is hard to be generated automatically. The best choice is still plain text with an agreed encoding (e.g. UTF-8) and line ending convention (e.g. LF, the "unix" way to terminate lines); however, for documents generated by source code, ASCII is likely the safer choice. TeX-based or HTML-based conventions for formatting are good ideas.

    • Deploy the doc to the "rest of the world" in a read-only format, like PDF (which is likely also the best choice) — this implies the ability to generate the PDF from the "private" doc.

    • Avoid translations: if your doc must be read "internationally", do it directly in english; in Europe, if it would be widespread, esperanto could be a fine choice... but alas it is still english that it is better to use! If people working in the project can't write/read "technical english" (to be distinguished from "literary english" and real-world english), teach it to them!


  • Writing the code...


    • Conventions! Train programmers to use an agreed set of conventions (tabs, spaces, indenting, symbols' and variables' names, comments' style...)

    • Comment, but not too much; avoid "line by line" comments, prefer "logical block" comments

    • This is not always true but... more data structures, less code!

    • Break functionalities into as small as possible functions/objects (depending on programming paradigm in use)

    • Think functions as reusable bricks, leave room for enhancements without breaking existing working code (this is easier in OO programming, nonetheless it is not impossible in non-OO programming)

    • Refactor your code; possibly before it becomes a mess; even when this happens, refactor it anyway, it is not a waste of time/money (at least not in the long run), it is needed to keep your code manageable and...

    • Refactor before your code becomes unmanageable, and refactor/redesign if your code is already hardly manageable; this sometimes could be done in small steps while developing other features

    • Use well-known (in the community of your language) libraries, rather than your own implementation (if there are no licensing issues); but of course if you can't find what you need, take your time to implement it rather than sticking to other possible but poor solutions

    • Don't waste time for optimizations, if your bottle-necks are elsewhere! (Profile your code to see where it is slow, and optimize there!)

    • If your language misses a hashtable and you need to choose actions according to a "pattern" given as input, ... find a hashtable implementation/library rather than using a long list of if-then-elseif or a C-like switch. Match this suggestion with the optimization topic if appropriate; if not, consider that the hash-approach produces clea(n/r)er code, as "more data structures, less code" does! (In fact the specific hashtable example is about following the suggestion "more data structures, less code", avoiding linear search.)

    • The previous reasoning on the hashtable is applicable to other features too, with the appropriate changes

    • Avoid copy-pasting; sometimes it is not so bad, but if you do it or need it too often, it means your code is badly designed/implemented... time for refactoring!

    • Test functionalities apart; e.g. write "toy code" and check its output given a specific input

    • Do not use so called Hungarian Notation; if to you it's an issue, use a good IDE to help you!

    • Use meaningful names for symbols/variables; but iterations in language having no special syntax to iterate over a "collection" (or a slice of it) can (should!) use short common names for indexes (like i j k n m).


2010-09-12

The "basic" thing

For a project with friends of mine I was requested to write a C# class. Ok, it is a good opportunity to learn in practice some bits of that language, I thought. But at the end what happened was that large part of the C# code has to be generated automatically by a VBA script (I wanted to do it in Perl, but since the code must be generated from MS Excel data and formulae, VBA was suggested as a easier and more microsoft-friendly solution), and so currently I've written more VBA code than C# code!

At the very beginning I was almost disgusted, but now I've realized that after all it was funny. It remembered me ancient days of "basic" structured programming, and having limitations that push you to find "workarounds" is funny too — though scaring if you have milestones!

I would like to give more time to C# enhancing the google video downloader, which is still a horrorful immature crap (though working for what it's worth); nonetheless now I can add VBA to the list of languages I've frequented someway... just another brick in the wall.

2010-07-20

Syntax and efficiency in C

Time for a brand new post.

In my last job interview I was asked to write a function to copy a string. Thinking not about compactness, I wrote on the fly the following code


void strcpy(const char *s, char *d)
{
while( *s != '\0' ) {
*d = *s; s++; d++;
}
*d = '\0';
}


That of course works, but it is longer than the need (and by the way, it is not compliant with what standard says about real strcpy, but this is not interesting since making a standard-compliant function was not the point of the question).

As noticed by the person making the interview, such a copy can be written in one line. When I've finished the interview and went back with the mind to what I've said and what I could have said, I realized that I could have defended my on-the-fly implementation: it is clearer (isn't it?), it has the same efficiency of the "compact" version, being this one possible just because of the great flexibility of the C syntax... which is, however, a boomerang if abused.

Second thought. The "long" code is clearer. Maybe (usually, not using C compactness ability makes code clearer; but likely in this particular case the one-line version is even clearer). But, my doubt is now: is it really the same with respect to efficiency?

Let us first see how to "convert" the long version to the short one. First, post-increments can be done "in place".


*d++ = *s++;


Then, assignment can be put into the while:


while( (*d++ = *s++) != '\0' ) ;


And here's the trap. We don't need the final "terminator-missing fix". Apparently, the short version looks also more efficient. But if we unroll the loops, we have the same amount of assignments, of course. Moreover, the short version assign the terminator reading it from the source; this gives two memory access (one for reading from s, one for writing into d).

On the other hand, the long version, does not need the extra reading access to memory and can be optimized on many processor with a single instruction writing a byte constant (read at the instruction fetch time).

So, it seems that is is the opposite: long version is (slightly) more efficient!

Of course modern compilers' optimization can cancel this apparent advantage. But let's look what gcc does with the basic "implicit" optimization level.

The "short version" (S) x86 code (with added comments):


mstrcpy:
pushl %ebp # prolog
movl %esp, %ebp
.L2:
movl 8(%ebp), %eax # get s in eax M
movzbl (%eax), %edx # *s in edx (dl) M
movl 12(%ebp), %eax # get d in eax M
movb %dl, (%eax) # *d = *s (dl) M
movl 12(%ebp), %eax # M
movzbl (%eax), %eax # take char*d in eax M
testb %al, %al # I
setne %al # is not 0? in the same al I
addl $1, 12(%ebp) # these are the post M
addl $1, 8(%ebp) # increments of d and s M
testb %al, %al # check al I
jne .L2 # loop if not 0 ('\0') JT
popl %ebp # epilog
ret


The "long version" (L) x86 code (with added comments)


mstrcpy2:
pushl %ebp # prolog
movl %esp, %ebp
jmp .L5 # uncond. jump
.L6:
movl 8(%ebp), %eax # get s in eax M
movzbl (%eax), %edx # *s in edx (dl) M
movl 12(%ebp), %eax # get d in eax M
movb %dl, (%eax) # *d = *s M
addl $1, 12(%ebp) # d++ M
addl $1, 8(%ebp) # s++ M
.L5:
movl 8(%ebp), %eax # get s M
movzbl (%eax), %eax # *s in eax (al) M
testb %al, %al # I
jne .L6 # exec body if not 0 JT
movl 12(%ebp), %eax # get d M
movb $0, (%eax) # *d = '\0' M
popl %ebp # epilog
ret


The M marks instructions that do memory access; let's count only the in-loop instructions first. S has 8 memory access instrs in loops; L has 8 memory access instrs in loop but S has 3 "register" instrs in loop, while L has none. So for S we have 8+3 in loop, for L only 8.

In S, when the '\0' is reached, the whole 8+3 instruction are performed; then the jump is not taken (JT stands for Jump Taken most of the times). In L, when the '\0' is found, (2+2)+1 instructions are executed (2 M after the .L5 label, and 2 M after the conditional jump). In fact S performs all the "actions" of the C code (post-increments inclusive) in the while; but L does only the test on *s and the final termination of d. (This means also that if we access s and d in the S version, they point one char beyond '\0', while in the L version we do not increment them, since we exit the loop, so s and d will point to the '\0').

On modern processors it is hard to say which is really more efficient. Likely, the difference in performance is negligible. Moreover, optimizations done by the compilers may level down the difference, if any.

It is however worth noting that L version does not produce a so longer code (considering also loop unrolling), and it could be even more efficient than S version!