Today, the format of an article is unusual for me: I rather ask the audience a question than share a ready-made recipe. However, I also propose a recipe to initiate a discussion. So today we are going to talk about the feeling of beauty.
I've been writing code for quite some time, and it just so happens that it's almost always in C ++. I can't even estimate how many times I've written such a construction:
for (int i=0; i<size; i++) {
[...]
}
Although why I can't, I really can:
find . \( -name \*.h -o -name \*.cpp \) -exec grep -H "for (" {} \; | wc -l
43641
Our current project contains 43 thousand cycles. I'm not the only one who saws the project, but the team is small and my project is not the first (and, I hope, not the last), so it will be used as a rough estimate. How for
good is this loop recording ? Indeed, in fact, it is not even important the number of times when I wrote the cycle , but the number of times when I read the cycle (see debugging and code review). And here we are obviously talking about millions.
At KPDV, a node is called the "perfection loop" .
So what is the perfect cycle?
What's the problem?
; , , , . , :
for (int iter=0; iter<nb_iter; iter++) { // some iterative computation
for (int c=0; c<mesh.cells.nb(); c++) // loop through all tetrahedra
for (int lv0=0; lv0<4; lv0++) // for every pair of
for (int lv1 = lv0+1; lv1<4; lv1++) // vertices in the tet
for (int d=0; d<3; d++) { // do stuff for each of 3 dimensions
nlRowScaling(weight);
nlBegin(NL_ROW);
nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d, 1);
nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);
nlEnd(NL_ROW);
}
[...]
}
, , . , , --, .
; . ( ) , for (int i=0; i<size; i++)
— : for
, .
for(;;)
, : , . , 0
size-1
, . , ? , — .
c++11 , :
#define FOR(I,UPPERBND) for(int I = 0; I<int(UPPERBND); ++I)
:
FOR(iter, nb_iter) {
FOR(c, mesh.cells.nb())
FOR(lv0, 4)
for (int lv1 = lv0+1; lv1<4; lv1++)
FOR(d, 3) {
nlRowScaling(weight);
nlBegin(NL_ROW);
nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d, 1);
nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);
nlEnd(NL_ROW);
}
[...]
}
, for (;;)
, , (, , ). FOR(,)
0
n-1
- . , , , (. ), (, , ).
, , , : " , ?"
11 , range for
? , . , :
for i in range(n):
print(i)
, range()
, . c++11 !
#include <iostream>
int main() {
int range[] = {0,1,2,3,4,5};
for (int i : range) {
std::cerr << i;
}
}
, , . C++ !
range(int n)
:
#include <iostream>
constexpr auto range(int n) {
struct iterator {
int i;
void operator++() { ++i; }
bool operator!=(const iterator& rhs) const { return i != rhs.i; }
const int &operator*() const { return i; }
};
struct wrapper {
int n;
auto begin() { return iterator{0}; }
auto end() { return iterator{n}; }
};
return wrapper{n};
}
int main() {
for (int i: range(13)) {
std::cerr << i;
}
return 0;
}
, int
vs size_t
, . gcc 10.2 -std=c++17 -Wall -Wextra -pedantic -O1
, ( ):
[...]
.L2:
mov esi, ebx
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
add ebx, 1
cmp ebx, 13
jne .L2
[...]
, , for (int i=0; i<13; i++)
.
, for (int i: range(n))
, FOR(,)
, , .
: enumerate
Range for c++11 . , , , :
#include <vector>
#include <iostream>
struct vec3 { double x,y,z; };
int main() {
std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};
for (vec3 &p: points) {
std::cerr << p.x;
}
return 0;
}
for (vec3 &p: points)
, , . , ? , . , , , :
std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};
std::vector<double> weights = {4,6,9};
int i = 0;
for (vec3 &p: points) {
std::cerr << p.x << weights[i++];
}
:
.L2:
movsd xmm0, QWORD PTR [r13+0]
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
movsd xmm0, QWORD PTR [rbp+0]
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
add rbp, 8
add r13, 24
cmp r14, rbp
jne .L2
, , , , , c++17 structural binding!
, :
#include <vector>
#include <iostream>
#include "range.h"
struct vec3 {
double x,y,z;
};
int main() {
std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};
std::vector<double> weights = {4,6,9};
for (auto [i, p]: enumerate(points)) {
std::cerr << p.x << weights[i];
}
return 0;
}
enumerate()
:
#ifndef __RANGE_H__
#define __RANGE_H__
#include <tuple>
#include <utility>
#include <iterator>
constexpr auto range(int n) {
struct iterator {
int i;
void operator++() { ++i; }
bool operator!=(const iterator& rhs) const { return i != rhs.i; }
const int &operator*() const { return i; }
};
struct wrapper {
int n;
auto begin() { return iterator{0}; }
auto end() { return iterator{n}; }
};
return wrapper{n};
}
template <typename T> constexpr auto enumerate(T && iterable) {
struct iterator {
int i;
typedef decltype(std::begin(std::declval<T>())) iterator_type;
iterator_type iter;
bool operator!=(const iterator& rhs) const { return iter != rhs.iter; }
void operator++() { ++i; ++iter; }
auto operator*() const { return std::tie(i, *iter); }
};
struct wrapper {
T iterable;
auto begin() { return iterator{0, std::begin(iterable)}; }
auto end() { return iterator{0, std::end (iterable)}; }
};
return wrapper{std::forward<T>(iterable)};
}
#endif // __RANGE_H__
-std=c++17 -Wall -Wextra -pedantic -O2
( ):
.L14:
movsd xmm0, QWORD PTR [rbx]
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
mov rdi, rax
mov rax, QWORD PTR [rsp+32]
movsd xmm0, QWORD PTR [rax+rbp]
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
add rbx, 24
add rbp, 8
cmp r12, rbx
jne .L14
And again, the compiler cleanly removed the wrapper (however, for this it was necessary to raise the optimization level from -O1
to -O2
).
By the way, it appeared in c ++ 20 std::ranges
, which makes it even easier to write such a function, but I'm not yet ready to switch to this standard.
Question to the audience
In your opinion, what should be the perfect cycle in 2020? Teach me!
If you have not asked this question yet, then copy the header file to your pet-project range.h
and try to use it for at least a few days.