#constexpr vs const on memory location

37 messages · Page 1 of 1 (latest)

quasi sphinx
#

Hey guys, I was watching a video on const constexpr consteval & constinit, and I was at the section justifying constexpr and in it one of the arguments seemed to go like this (paraphrasing massively):

having 3.14f in our code is bad because we have hardcoded the value of pi... We can pull the value of pi into a global constant, but the downside is now that everytime we call area (area_variable) we will need to access a memory address, making the function ever so slighly more expansive
It goes on about how this can be fixed by macros but constexpr server this purpose better... Anyway, from looking at this in compiler explorer

const double pi = 3.15f;

double area(const double radius) {
  return 3.14f * (radius * radius);
}

double area_variable(const double radius) {
  return pi * (radius * radius);
}

Both of these seem identical with -O0 flag, is there a way to make them be different, or was this just a case of "true in general but not for the demonstrative example"?

azure jackalBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

serene latch
#

that's doing a memory access reading the constant from memory

#

In general it's useless to look at -O0, unoptimized code is stupid

#

You will find though in this case the optimized code is identical though

#

That's because the compiler can constant propagate the const value of pi, even though it's not constexpr

quasi sphinx
#

I was just trying to observe what was mentioned in the video, which said that in the case where you don't pull out the number into a const variable, it should be faster, since no memory access would be needed

serene latch
#

A lot of people misunderstand what constexpr means, but it is useful on a variable in this case because it forces the value to be something that can be and is computed at compile time

quasi sphinx
serene latch
#

There is, in both functions and even under optimization

quasi sphinx
#

So I guess this qualifies as "true in general but not for the demonstrative example"?

serene latch
#

Because of how floats work on x86 constants are (almost) always loaded from memory

#

It would maybe be better to look at an integer example

#

;asm

const int pi = 3;

int area(const int radius) {
  return 4 * (radius * radius);
}

int area_variable(const int radius) {
  return pi * (radius * radius);
}
nimble finchBOT
#
Assembly Output
area(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  sal eax, 2
  pop rbp
  ret
area_variable(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  mov edx, eax
  mov eax, edx
  add eax, eax
  add eax, edx
  pop rbp
  ret

serene latch
#

;asm

constexpr int pi = 3;

int area(const int radius) {
  return 4 * (radius * radius);
}

int area_variable(const int radius) {
  return pi * (radius * radius);
}
nimble finchBOT
#
Assembly Output
area(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  sal eax, 2
  pop rbp
  ret
area_variable(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  mov edx, eax
  mov eax, edx
  add eax, eax
  add eax, edx
  pop rbp
  ret

serene latch
#

;asm

int pi = 3;

int area(const int radius) {
  return 4 * (radius * radius);
}

int area_variable(const int radius) {
  return pi * (radius * radius);
}
nimble finchBOT
#
Assembly Output
pi:
  .long 3
area(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  sal eax, 2
  pop rbp
  ret
area_variable(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  mov edx, eax
  mov eax, DWORD PTR pi[rip]
  imul eax, edx
  pop rbp
  ret

serene latch
#

You'll notice there's no difference between the const and constexpr versions, this is because the compiler can constant propagate just fine in both cases

#

There is a difference without const though, because then the compiler doesn't know that the value will always be 3

#

But in general constexpr is appropriate on pi because the intent is for it to be a compile-time constant and constexpr ensures the right-hand side of the assignment is constexpr

quasi sphinx
#

Yeah I see now

#

I learned this while googling "how to disable all optimizations" in cpp that you can add volatile to things so that it doesn't optimize, in this case I think it exactly describes the behaviour mentioned in the video

#

;asm

volatile const int pi = 30;

int area(const int radius) {
  return 42 * (radius * radius);
}

int area_variable(const int radius) {
  return pi * (radius * radius);
}
nimble finchBOT
#
Assembly Output
pi:
  .long 30
area(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  imul eax, eax, 42
  pop rbp
  ret
area_variable(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  imul eax, eax
  mov edx, eax
  mov eax, DWORD PTR pi[rip]
  imul eax, edx
  pop rbp
  ret

quasi sphinx
#

I think that is what he meant, it is just that as you mentioned it always gets optimized

serene latch
#

Volatile should almost never be used in general, it's another thing most people seem to misunderstand

quasi sphinx
#

This is just for illustrative purposes, I probably would want optimization in a real program

#

This is the article I saw this being used

#

Oh right, I appreciate the help! Need to mark as solved

#

!solved

azure jackalBOT
#

Thank you and let us know if you have any more questions!

This thread is now set to auto-hide after an hour of inactivity