GCC 5.1 Loop unrolling - c

Given the following code
#include <stdio.h>
int main(int argc, char **argv)
{
int k = 0;
for( k = 0; k < 20; ++k )
{
printf( "%d\n", k ) ;
}
}
Using GCC 5.1 or later with
-x c -std=c99 -O3 -funroll-all-loops --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
does partially loop unrolling, it unrolls the loop ten times and then does a conditional jump.
.LC0:
.string "%d\n"
main:
pushq %rbx
xorl %ebx, %ebx
.L2:
movl %ebx, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 1(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 2(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 3(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 4(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 5(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 6(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 7(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 8(%rbx), %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
leal 9(%rbx), %esi
xorl %eax, %eax
movl $.LC0, %edi
addl $10, %ebx
call printf
cmpl $20, %ebx
jne .L2
xorl %eax, %eax
popq %rbx
ret
But using older versions of GCC such as 4.9.2 creates the desired assemlby
.LC0:
.string "%d\n"
main:
subq $8, %rsp
xorl %edx, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $1, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $2, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $3, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $4, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $5, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $6, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $7, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $8, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $9, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $10, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $11, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $12, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $13, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $14, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $15, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $16, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $17, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $18, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
movl $19, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
xorl %eax, %eax
addq $8, %rsp
ret
It there a way to force the later versions of GCC to produce the same output?
Using https://godbolt.org/g/D1AR6i to produce the assembly
EDIT: No duplicated question, since the problem to completly unroll loops with later versions of GCC has not yet been solved. Passing --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000 has not effects on the generated assembly using GCC >= 5.1

The flags and parameters you are using do not guarantee that the loops will be completely unrolled. The GCC documentation states the following regarding the -funroll-all-loops flag you are using:
turns on complete loop peeling (i.e. complete removal of loops with a
small constant number of iterations)
If the compiler decides that the number of iterations for a given piece of code is not "a small constant" (i.e. number is too high), it may only do partial peeling or unrolling as it has done here. Furthermore, the param options you are using are only maximum values, but do not force complete unrolling for loops smaller than the set value. In other words, if a loop has more iterations than the maximum you have set, then the loop will not to be completely unrolled; but the inverse is not true.
Many factors are taken into account when doing optimisations. Here the bottleneck in your code is the call to printf function, and the compiler will probably take this into account when doing its cost calculations, or judge that the instruction size overhead for unrolling is too important. As you are nevertheless telling it to unroll loops, it seems to determine that the best solution is to transform the initial loop with 10 unrolls and a a jump.
If you replace printf with something else, the compiler may optimise differently. For instance try replacing it with the following:
volatile int temp = k;
The loop with this new code snippet will be fully unrolled on the newer versions of GCC (and the older ones as well). Note that the volatile keyword is just a trick used so the compiler does not optimise out the loop completely.
To sum up, to the best of my knowledge there is no way to force later versions of GCC to produce the same output.
As a side note, from optimisation level -O2 onwards and without any additional compiler flags, recent versions of Clang fully unroll your loop.

Related

What do these 2 instructions do in the following function?

So I'm pretty much a noob in IA32 assembly language. I tried compiling this C function into IA32 assembly (-mpreferred-stack-boundary=2):
__attribute__((cdecl))
int odd_sum(int a[], int n, int sum) {
if (n == 0) return sum;
else if ((a[n-1] % 2) == 0)
return odd_sum(a, n-1, sum);
else return odd_sum(a, n-1, sum + a[n-1]);
}
and the GCC outputs this:
.file "test.c"
.text
.globl _odd_sum
.def _odd_sum; .scl 2; .type 32; .endef
_odd_sum:
LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $12, %esp
cmpl $0, 12(%ebp)
jne L2
movl 16(%ebp), %eax
jmp L3
L2:
movl 12(%ebp), %eax
addl $1073741823, %eax
leal 0(,%eax,4), %edx
movl 8(%ebp), %eax
addl %edx, %eax
movl (%eax), %eax
andl $1, %eax
testl %eax, %eax
jne L4
movl 12(%ebp), %eax
leal -1(%eax), %edx
movl 16(%ebp), %eax
movl %eax, 8(%esp)
movl %edx, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _odd_sum
jmp L3
L4:
movl 12(%ebp), %eax
addl $1073741823, %eax
leal 0(,%eax,4), %edx
movl 8(%ebp), %eax
addl %edx, %eax
movl (%eax), %edx
movl 16(%ebp), %eax
addl %eax, %edx
movl 12(%ebp), %eax
subl $1, %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _odd_sum
L3:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE0:
.ident "GCC: (MinGW.org GCC-8.2.0-3) 8.2.0"
What I am not able to comprehend are these 2 lines:
addl $1073741823, %eax
leal 0(,%eax,4), %edx
I understand those 2 lines should have something to do with the a[n-1], but I can't seem to be able to understand what exactly they do in the process. Can someone help me with this problem please?
It is just a fancy way of computing the offset into the array a[n-1].
1073741823 is 0x3fffffff. If n is 3, for example, it will add them and get 0x40000002. Then it multiplies by 4 with the second instruction, which results in 0x00000008, discarding the top bits.
So we are left with an offset of 8 bytes, which is exactly the offset (in bytes) that you need for a[n-1], i.e. a[2] (when the size of an int is 4 bytes).
To get a more understandable output with the -S flag:
create assembler code:
c++ -S -fverbose-asm -g -O2 (other optimizaton flags) test.cc -o test.s
create asm interlaced with source lines:
as -alhnd test.s > test.lst

Why does GCC produce the following asm output?

I don't understand why gcc -S -m32 produces these particular lines of code:
movl %eax, 28(%esp)
movl $desc, 4(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call sort_gen_asm
My question is why %eax is pushed and then popped? And why movl used instead of pushl and popl respectively? Is it faster? Is there some coding convention I don't yet know? I've just started looking at asm-output closely, so I don't know much.
The C code:
void print_array(int *data, size_t sz);
void sort_gen_asm(array_t*, comparer_t);
int main(int argc, char *argv[]) {
FILE *file;
array_t *array;
file = fopen("test", "rb");
if (file == NULL) {
err(EXIT_FAILURE, NULL);
}
array = array_get(file);
sort_gen_asm(array, desc);
print_array(array->data, array->sz);
array_destroy(array);
fclose(file);
return 0;
}
It gives this output:
.file "main.c"
.section .rodata
.LC0:
.string "rb"
.LC1:
.string "test"
.text
.globl main
.type main, #function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $.LC0, 4(%esp)
movl $.LC1, (%esp)
call fopen
movl %eax, 24(%esp)
cmpl $0, 24(%esp)
jne .L2
movl $0, 4(%esp)
movl $1, (%esp)
call err
.L2:
movl 24(%esp), %eax
movl %eax, (%esp)
call array_get
movl %eax, 28(%esp)
movl $desc, 4(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call sort_gen_asm
movl 28(%esp), %eax
movl 4(%eax), %edx
movl 28(%esp), %eax
movl (%eax), %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call print_array
movl 28(%esp), %eax
movl %eax, (%esp)
call array_destroy
movl 24(%esp), %eax
movl %eax, (%esp)
call fclose
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu8) 4.8.1"
.section .note.GNU-stack,"",#progbits
The save / load of eax is because you did not compile with optimizations. So any read/write of a variable will emit a read/write of a memory address.
Actually, for (almost) any line of code you will be able to identify the exact piece of assembler code resulting from it (let me advise you to compile with gcc -g -c -O0 and then objdump -S file.o):
#array = array_get(file);
call array_get
movl %eax, 28(%esp) #write array
#sort_gen_asm(array, desc);
movl 28(%esp), %eax #read array
movl %eax, (%esp)
...
About not pushing/poping, it is a standard zero-cost optimization. Instead of push/pop every time you want to call a function you just substract the maximum needed space to esp at the beginning of the function and then save your function arguments at the bottom of the empty space. There are a lot of advantages: faster code (no changing esp), it doesn't need to compute the argument in any particular order, and the esp will need to be substracted anyway for the local variables space.
Some things have to do with calling conventions. Others with optimisations.
sort_gen_asm seems to use cdecl calling convention which requires it's arguments to be pushed onto the stack in reverse order. thus:
movl $desc, 4(%esp)
movl %eax, (%esp)
The other moves are partially unoptimised compiler routines:
movl %eax, 28(%esp) # save contents of %eax on the stack before calling
movl 28(%esp), %eax # retrieve saved 28(%esp) in order to prepare it as an argument
# Unoptimised compiler seems to have forgotten that it's
# still in the register

Allocation of bytes for local variables of a function and Is the POP instruction called only once

I'm trying to understand the assembly code during a recursive function call.
#include<stdio.h>
int recursive(int no){
if(no > 1){
no--;
recursive(no);
printf("\n %d \n",no);
}
else if(no == 1){
return 1;
}
}
int main(){
int a = 10;
recursive(a);
return 0;
}
disassembly :
.file "sample2.c"
.section .rodata
.LC0:
.string "\n %d \n"
.text
.globl recursive
.type recursive, #function
recursive:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
cmpl $1, 8(%ebp)
jle .L2
subl $1, 8(%ebp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call recursive
movl $.LC0, %eax
movl 8(%ebp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
jmp .L5
.L2:
cmpl $1, 8(%ebp)
jne .L5
movl $1, %eax
movl %eax, %edx
movl %edx, %eax
jmp .L4
.L5:
.L4:
leave
ret
.size recursive, .-recursive
.globl main
.type main, #function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $10, 28(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call recursive
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5"
.section .note.GNU-stack,"",#progbits
I could understand .LC0 always holds the string literals. But I dont know what it really means. Would like to understand the code during the function call recursion was made.
I could not understand what this piece of assembly code does,
subl $24, %esp
cmpl $1, 8(%ebp)
jle .L2
subl $1, 8(%ebp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call recursive
movl $.LC0, %eax
movl 8(%ebp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
jmp .L5
.L2:
cmpl $1, 8(%ebp)
jne .L5
movl $1, %eax
movl %eax, %edx
movl %edx, %eax
jmp .L4
Q1:
The recursive function contains 1 parameter. so after the padding alignment, it has to be 8. why is it 24.
Also in .L2 ,
movl $1, %eax
movl %eax, %edx
movl %edx, %eax
jmp .L4
Q2:
we have moved '1' to the accumulater, why are we moving again to data register and then back to the accumulator.
Q3:
Are we popping out of stack. If leave is used for popping out of stack, are we not popping the rest of the 8 stack frames ?
To answer the only thing in your post that matches your title:
Why are we not popping out from the stack and only push instruction in the assembly.
Because leave is equivalent to:
movl %ebp, %esp
popl %ebp

Assembly language program to find the GCD - Floating point exception

With this program, I am intending to find the GCD of two numbers. But the result I get is "Floating point exception(core dumped)". What is the problem?
The code I am trying to generate is
int main() {
int sml, lrg, rem;
read %d sml
read %d lrg
while (sml > 0){
rem = lrg % sml;
lrg = sml;
sml = rem;
}
print %d lrg;
return 0;
}
The assembly file generated by me is:
.file "gcd.c"
.section .rodata
.LC0:
.string "%d"
.LC1:
.string "%d\n"
.text
.globl main
.type main, #function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
leal -8(%ebp), %eax #scan a value
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call scanf
leal -12(%ebp), %eax #scan a value
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call scanf
.L2:
movl $0, %eax
cmpl -8(%ebp),%eax
jle .L0
jmp .L1
.L0:
movl -12(%ebp),%eax
movl -8(%ebp),%ecx
movl %eax,%edx
sarl $31, %edx
idivl %ecx
movl %edx,%eax
movl %eax, -16(%ebp)
movl -8(%ebp),%edx
movl %edx, -12(%ebp)
movl -16(%ebp),%edx
movl %edx, -8(%ebp)
jmp .L2
.L1:
movl -12(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl $0, %edx
movl $0, %eax #end of program
leave
ret
.LFE2:
.size main, .-main
.ident "GCC: (GNU) 4.2.3 (4.2.3-6mnb1)"
.section .note.GNU-stack,"",#progbits
On the other hand, this assembly code works
.file "check.c"
.section .rodata
.LC0:
.string "%d"
.text
.globl main
.type main, #function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
leal 20(%esp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call scanf
leal 24(%esp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call scanf
jmp .L2
.L3:
movl 24(%esp), %eax
movl 20(%esp), %ecx
movl %eax, %edx
sarl $31, %edx
idivl %ecx
movl %edx, 28(%esp)
movl 20(%esp), %eax
movl %eax, 24(%esp)
movl 28(%esp), %eax
movl %eax, 20(%esp)
.L2:
movl 20(%esp), %eax
testl %eax, %eax
jg .L3
movl 24(%esp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",#progbits
You probably have a division by zero in this line:
idivl %ecx
With a 0 value in ecx register.
Your comparison and jump are wrong. The working code has:
testl %eax, %eax
jg .L3
The broken code has:
movl $0, %eax
cmpl -8(%ebp),%eax
jle .L0
jmp .L1
The former compares %eax (which contains the most recently computed residue) to zero, and it continues the loop (jumps to .L3) if the residue is positive (greater than zero).
The latter compares 0 to -8(%ebp) (the most recently computed residue). Note that the order is different; it compares 0 to -8(%ebp), not -8(%ebp) to 0. The testl instruction compares the value (after performing an AND) with zero. If the value is positive, it is “greater than” zero. The cmpl instruction compares its second operand to its first operand; if the second operand exceeds the first, the result is “greater than”. This is because, in Intel’s manuals and assembly language, instructions are written with their operands in the reverse order. E.g, moving 3 into %eax would be “mov %eax, $3”. However, the assembler you are using reverses all the operands from Intel’s order (due to legacy reasons).
So, the broken code continues the loop (jumps to .L0) if 0 is less than or equal to the residue. Thus, if the residue is zero, the loop continues. You can change the jle to jl:
jl .L0
Alternately, you could eliminate the redundant unconditional jump:
movl $0, %eax
cmpl -8(%ebp),%eax
jge .L1
Also, you probably want to change:
movl $.LC0, (%esp)
call _printf
to:
movl $.LC1, (%esp)
call _printf
so that you pass "%d\n" to printf instead of passing "%d".
Incidentally, the idivl instruction generates a divide error, not a floating-point exception. Your system is misreporting the error.

C - Is there a way to write this in less code?

Edit : I understand the confusion. But I am not trying to optimize here, as #sergio said, I could not come up with a better word.
--
I have been writing code in JavaScript and PHP for a long time now, that I find it hard sometimes to optimize my code in C.
What I mean by optimizing is writing a program in less code. here is an example:
int i;
srand(time(NULL));
for(i = 0; i < 10; i++){
printf(" %d ", rand() % 300);
if(i < 10 - 1){
printf("|");
}
}
in Javascript I would have wrote it this way :
var html = ''
for(var i = 0; i < 10; i++){
html += ' '+Math.floor(Math.random() * 100)+' '+( i == 9 ? '|' : '' )
}
the difference in C is that I had to do the If in an other line, and could not act inline on the string. I hope you get my point.
So how would you write my code?
Thank you.
"Number of lines" is traditionally a poor judge of code, if you cram too much into one line it gets unreadable.
for(i = 0; i < 10; i++)
printf("%s %d ", i ? "|" : "", rand() % 300);
Optimizing and writing minimum code are different.
In C, you could use the ternary operator instead of your if statement if you want to just condense the code.
The assembly code generated and its efficiency however probably aren't changing as long as you have the same 1 conditional within 1 loop running N times, no matter how cool it looks, so focus on the algorithm, and not how concise the code is.
This answer is in response to Murilo Vasconcelos:
Use http://gcc.godbolt.org/ to follow along.
#include <stdio.h>
#include <ctime>
#include <cstdlib>
int main() {
int i;
srand(time(NULL));
for(i=0; i< 10; i++){
printf(" %d ", rand() % 300);
if(i < 10 - 1){
printf("|");
}
}
}
Generates the following assembly using g++-4.8:
.LC0:
.string " %d "
main:
pushq %rbp
xorl %edi, %edi
movl $458129845, %ebp
pushq %rbx
xorl %ebx, %ebx
subq $8, %rsp
call time
movl %eax, %edi
call srand
call rand
movl $458129845, %edx
movl $.LC0, %edi
movl %eax, %esi
imull %edx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
.L2:
movl $124, %edi
addl $1, %ebx
call putchar
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebp
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
cmpl $9, %ebx
jne .L2
addq $8, %rsp
xorl %eax, %eax
popq %rbx
popq %rbp
ret
On the other hand this code:
#include <stdio.h>
#include <ctime>
#include <cstdlib>
int main() {
int i;
srand(time(NULL));
for (i = 0; i < 9; i++) {
printf(" %d |", rand() % 300);
}
printf(" %d ", rand() % 300);
}
Generates this assembly:
.LC0:
.string " %d |"
.LC1:
.string " %d "
main:
pushq %rbx
xorl %edi, %edi
movl $458129845, %ebx
call time
movl %eax, %edi
call srand
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $5, %edx
sarl $31, %eax
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC0, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
call rand
movl $.LC1, %edi
movl %eax, %esi
imull %ebx
movl %esi, %eax
sarl $31, %eax
sarl $5, %edx
subl %eax, %edx
xorl %eax, %eax
imull $300, %edx, %edx
subl %edx, %esi
call printf
xorl %eax, %eax
popq %rbx
ret
In other words, by changing the loop it allowed the compiler to unroll the loop, which should be a rather large performance increase that you wouldn't get without your change. So don't let people poo poo you. Check for yourself what the assembly becomes, not all hand optimization is a waste of time. And, of course, test test test.
But, of course, you shouldn't optimize prematurely. You should follow your profiler and let it tell you what your hot spots are, and where you need to optimize.
Better Algorithms first.
Then better code.
Then better assembly.
EDIT: the OP edited the question.
If you want just to make an inline code:
int i = 0;
for (srand(time(NULL)); i < 10; printf("%d %s ", rand() % 300, (i++ < 9 ? "|" : "")));

Resources