web-dev-qa-db-fra.com

Boucle de macro auto-déroulante en C/C++

Je travaille actuellement sur un projet où chaque cycle compte. Lors du profilage de mon application, j'ai découvert que le temps système de certaines boucles internes était assez élevé, car elles ne se composaient que de quelques instructions machine. De plus, le nombre d'itérations dans ces boucles est connu au moment de la compilation.

J'ai donc pensé qu'au lieu de dérouler manuellement la boucle avec copier-coller, je pourrais utiliser des macros pour dérouler la boucle au moment de la compilation, afin de pouvoir la modifier facilement ultérieurement.

Ce que je l'image est quelque chose comme ça:

#define LOOP_N_TIMES(N, CODE) <insert magic here>

Pour que je puisse remplacer for (int i = 0; i < N, ++i) { do_stuff(); } par:

#define INNER_LOOP_COUNT 4
LOOP_N_TIMES(INNER_LOOP_COUNT, do_stuff();)

Et il se déroule pour:

do_stuff(); do_stuff(); do_stuff(); do_stuff();

Comme le préprocesseur C est encore un mystère pour moi la plupart du temps, je ne sais pas comment y parvenir, mais je sais que cela doit être possible, car Boost semble avoir une macros BOOST_PP_REPEAT. Malheureusement, je ne peux pas utiliser Boost pour ce projet.

11
Karsten

Vous pouvez utiliser des modèles pour dérouler. Voir le démontage pour l'échantillon Live on Godbolt

enter image description here

Mais -funroll-loops a le même effet pour cet exemple .


Live On Colir

template <unsigned N> struct faux_unroll {
    template <typename F> static void call(F const& f) {
        f();
        faux_unroll<N-1>::call(f);
    }
};

template <> struct faux_unroll<0u> {
    template <typename F> static void call(F const&) {}
};

#include <iostream>
#include <cstdlib>

int main() {
    srand(time(0));

    double r = 0;
    faux_unroll<10>::call([&] { r += 1.0/Rand(); });

    std::cout << r;
}
22
sehe

Vous pouvez utiliser le pré-processeur et jouer à quelques astuces avec la concaténation de jetons et le développement de macros multiples, mais vous devez coder en dur toutes les possibilités:

#define M_REPEAT_1(X) X
#define M_REPEAT_2(X) X X
#define M_REPEAT_3(X) X X X
#define M_REPEAT_4(X) X X X X
#define M_REPEAT_5(X) X M_REPEAT_4(X)
#define M_REPEAT_6(X) M_REPEAT_3(X) M_REPEAT_3(X)

#define M_EXPAND(...) __VA_ARGS__

#define M_REPEAT__(N, X) M_EXPAND(M_REPEAT_ ## N)(X)
#define M_REPEAT_(N, X) M_REPEAT__(N, X)
#define M_REPEAT(N, X) M_REPEAT_(M_EXPAND(N), X)

Et puis développez-le comme ceci:

#define THREE 3

M_REPEAT(THREE, three();)
M_REPEAT(4, four();)
M_REPEAT(5, five();)
M_REPEAT(6, six();)

Cette méthode nécessite des nombres littéraux, vous ne pouvez pas faire quelque chose comme ceci:

#define COUNT (N + 1)

M_REPEAT(COUNT, stuff();)
11
M Oehm

Il n'y a pas de moyen standard de faire cela.

Voici une approche légèrement dingue:

#define DO_THING printf("Shake it, Baby\n")
#define DO_THING_2 DO_THING; DO_THING
#define DO_THING_4 DO_THING_2; DO_THING_2
#define DO_THING_8 DO_THING_4; DO_THING_4
#define DO_THING_16 DO_THING_8; DO_THING_8
//And so on. Max loop size increases exponentially. But so does code size if you use them. 

void do_thing_25_times(void){
    //Binary for 25 is 11001
    DO_THING_16;//ONE
    DO_THING_8;//ONE
    //ZERO
    //ZERO
    DO_THING;//ONE
}

Ce n’est pas trop demander à un optimiseur d’éliminer le code mort. Dans ce cas:

#define DO_THING_N(N) if(((N)&1)!=0){DO_THING;}\
    if(((N)&2)!=0){DO_THING_2;}\
    if(((N)&4)!=0){DO_THING_4;}\
    if(((N)&8)!=0){DO_THING_8;}\
    if(((N)&16)!=0){DO_THING_16;}
5
Persixty

Vous ne pouvez pas utiliser une construction #define pour calculer le "décompte". Mais avec suffisamment de macros, vous pouvez définir ceci:

#define LOOP1(a) a
#define LOOP2(a) a LOOP1(a)
#define LOOP3(a) a LOOP2(a)

#define LOOPN(n,a) LOOP##n(a)

int main(void)
{
    LOOPN(3,printf("hello,world"););
}

Testé avec VC2012

2
harper

Vous ne pouvez pas écrire real instructions récursives avec des macros et je suis à peu près sûr que vous ne pouvez pas non plus avoir real itération dans les macros.

Cependant, vous pouvez consulter Order . Bien qu'il soit entièrement construit sur le préprocesseur C, il "implémente" des fonctionnalités de type itération. En fait, il peut avoir jusqu'à N itérations, où N est un grand nombre. Je suppose que c'est similaire pour les macros "récursives". Quoi qu’il en soit, la situation est telle que peu de compilateurs le prennent en charge (GCC est l’un d’eux cependant).

0
dmg