Reputation: 79208
I am new to C and getting a feel for what is most optimized and what is the correct way to deal with pointers, values, references, etc..
I have started by creating a simple integer add
function.
int
add(int a, int b) {
return a + b;
}
void
main() {
// these work
int sum = add(1, 1);
int a = 1;
int b = 1;
int c = add(a, b);
// this doesn't
int d = add(&a, &b);
int e = add(*a, *b);
}
From my understanding doing add(a, b)
will copy the values into the function, which means it's slower performance-wise than passing in pointers. So I try to create two add functions, renaming this one to add_values
.
int
add_pointers(int *a, int *b) {
return (*a) + (*b);
}
int
add_values(int a, int b) {
return a + b;
}
void
main() {
// these work
int sum = add_values(1, 1);
int a = 1;
int b = 1;
int c = add_values(a, b);
// this works now
int d = add_pointers(&a, &b);
// not sure
int e = add(*a, *b);
}
I'm wondering a few things:
add_pointers
because nothing is copied. But then you can't do a simple add(1, 1)
, so that's no fun from an API standpoint.Upvotes: 2
Views: 2324
Reputation: 12670
From my point of view, depending on if you have a compiler capable of making inline
function expanding, the quickest way to do it to simply:
inline int add(int a, int b)
{
return a + b;
}
because the compiler probably will mostly avoid the subroutine call/return at all and will use the best expansion available in each use case (in most cases, this will be inlined as a single add r3, r8
instruction.) This can take well less than a single clock cycle in many today multicore and pipelined cpus.
In case you don't have access to such a compiler, then probably the best way to simulate that scenario is to:
#define add(a,b) ((a) + (b))
and you'll be inlining, while maintaining the function notation. But the premises fail with this response, as you asked for a function, depending on the priority of the premises :)
When you think on the best way to do a function call, first think that, for small functions, the heaviest task you make is to do a subroutine call at all... as it takes time to push the return address, think that in this case, the worst part is to have to call a function, just to add it's two parameters (adding two values stored in registers requires only one instruction, but with a function call, it requires at least three ---the call, the add, and the return back) The add doesn't require much time if the addends are already in registers, but a subroutine call normally requires to push a register in the stack and to pop it later. Those are two memory accesses, that will cost more, even if cached in the instruction cache.
Of course, if the compiler knows that the function is cacheable, and you use it several times with the same parameters in one expression of in the same block, it can cache the result value to be used later, and save the cost of making the sum again. The thing becomes as if the best way of proceeding was to see which exact scenario we are dealing with. But at this point, the major cost of adding two numbers, is by far, the cost of jailing it in a function envelope.
I tried the following example, and compiled it in an arm7 architecture (raspberry pi B+, freebsd, clang compiler) and the result was far than good :)
inline int sum(int a, int b)
{
return a + b;
}
int main()
{
int a = 3, b = 2, c = sum(a, b);
}
resulting into:
/* ... */
.file "inline.c"
.globl main @ -- Begin function main
.p2align 2
.type main,%function
.code 32 @ @main
main:
.fnstart
@ %bb.0:
mov r0, #0
bx lr
.Lfunc_end0:
.size main, .Lfunc_end0-main
As you see, the only code for main consisted in storing 0
return value in r0
(exit code) ;)
Just in case i compiled add
as an external library function:
int add(int a, int b);
int main()
{
int a = 3, b = 2, c = sum(a, b);
}
would result in:
.file "inline.c"
.globl main @ -- Begin function main
.p2align 2
.type main,%function
.code 32 @ @main
main:
.fnstart
@ %bb.0:
.save {r11, lr}
push {r11, lr}
.setfp r11, sp
mov r11, sp
mov r0, #3
mov r1, #2
bl sum <--- the call to the function.
mov r0, #0
pop {r11, pc}
.Lfunc_end0:
.size main, .Lfunc_end0-main
.cantunwind
.fnend
You can see that the call to the function will be made, anyway, as the compiler has not been informed of the kind of function it has at hand (even if the result code is not going to be used, as the function can have lateral effects), and has to be called, anyway.
By the way, and as mentioned, the way of passing references to a function involves dereferencing those pointers, and that means normally memory accesses (which is far more expensive than adding two registers together)
Upvotes: 1
Reputation: 213513
which means it's slower performance-wise than passing in pointers
That's where you got it wrong. On a typical 32 bit computer, int
are 32 bit and pointers are 32 bit. So the actual amount of data passing is identical between both versions. However, use of pointers can boil down to indirect access machine code, so it might actually yield less inefficient code in some circumstances. In the general case int add(int a, int b)
is probably the most efficient.
As a rule of thumb, it is fine to pass all standard integer and float types by value to functions. But structs or unions should be passed through pointers.
In this particular case the compiler is likely to "inline" the whole function, replacing it with a single addition instruction in the machine code. After which the whole parameter passing turns into a non-issue.
Overall, don't ponder performance too much as a beginner, it is an advanced topic and depends on the specific system. Instead, focus on writing as readable code as possible.
Upvotes: 1
Reputation: 223699
Anytime you call a function with parameters you're copying the values of the parameters. In your examples it's just a matter of whether you're copying pointer values or integer values. Copying an int
will not be noticeably faster or slower than copying a pointer, but with a pointer you have an additional read from memory whenever you dereference the pointer.
For any simple data type, you're better of just accepting parameters by value. The only time it makes more sense to pass a pointer is if you're dealing with an array or struct
which can be arbitrarily large.
Upvotes: 1