Código C ++ para generar DAG aleatorios

Jan 11 2021

Escribí el siguiente código C ++ hace un tiempo para generar gráficos aleatorios para un proyecto en el que estaba trabajando:

#include "stdafx.h"
#include <iostream>
#include <algorithm>    // std::min_element, std::max_element
#include <sstream>
#include <fstream>
#include <string>
#include <iterator>
#include <random>
#include <vector>



#define NoOfNodes 30


struct GPU_data
{
    int number_Copies;
    int workItems;
    int workGroups;
    bool memory;
    double power_consumption;
    double execTime;
};


struct DAG_data
{
    int processid; //Node's ID
    int PEid; //Processor's ID to which node is assigned
    std::vector<GPU_data> Node_config;
    int precede;
    int follow; //nodes following this node
    int noOfCopies;
    double transData;
    double ExecTime;
    double powerDraw;
};

void CreateandAssignEdges(DAG_data Sample, int NoOfEdges)
{
    
    unsigned int i = 0;

    if (Sample.processid == 0)
    {
        //parent process- so there will be no edges
        Sample.precede = 0;
        Sample.follow = rand()% NoOfEdges + 1;
    }

    else if (Sample.processid == NoOfNodes - 1)
    {
        //sink process- so there will be no following edges
        Sample.follow = 0;
    }

    else
    {
        //which nodes will the edges connect to (Anywhere from among the following nodes, including the sink node)
        Sample.follow = (Sample.processid + 1) + (std::rand() % (29 - (Sample.processid) + 1));

        if (Sample.follow == 30)
        {
            Sample.follow -= 1;
        }
    }

}

DAG_data EdgeAssignment(DAG_data Sample, int NoOfEdges)
{

    unsigned int i = 0;

    if (Sample.processid == 0)
    {
        //parent process- so there will be no edges
        Sample.precede = 0;
        Sample.follow = rand() % NoOfEdges + 1;

        return Sample;
    }

    else if (Sample.processid == NoOfNodes - 1)
    {
        //sink process- so there will be no following edges
        Sample.follow = 0;

        return Sample;
    }

    else
    {
        //which nodes will the edges connect to (Anywhere from among the following nodes, including the sink node)
        Sample.follow = (Sample.processid + 1) + (std::rand() % (29 - (Sample.processid) + 1));

        return Sample;
    }

}


        //Sample->precede = rand() % NoOfEdges;
        //Sample->follow = rand() % NoOfEdges;

        ////Preceding and following edges of a node should not be the same.
        //while (Sample->precede > Sample->follow || Sample->precede == Sample->follow)
        //{
        //  //assign both edges again
        //  Sample->follow = rand() % NoOfEdges;
        //  Sample->precede = rand() % NoOfEdges;
        //}

    



void whenPEisGPU(DAG_data Sample, int processorID)
{
    GPU_data emptySet;
    int i = 0;
    int NoOfConfigs = rand() % 5;
    GPU_data* sub_tasks = &emptySet;
    while (i != NoOfConfigs)
    {
        sub_tasks->memory = rand() % 1;
        sub_tasks->number_Copies = rand() % 3;
        sub_tasks->workGroups = rand() % 10 +1;
        sub_tasks->workItems = rand() % (sub_tasks->workGroups * 2) + 1;
        sub_tasks->power_consumption = rand() % 250;
        sub_tasks->execTime = rand() % (int)(Sample.ExecTime / 2);
        Sample.Node_config.push_back(*sub_tasks);
        i++;
    }
}

void PESpecificParameters(DAG_data Sample, int processorID)
{
    if (processorID == 0)
    {
        Sample.ExecTime = rand() % 100;
        Sample.powerDraw = 0.0;
        Sample.noOfCopies = 0;

    }
    else if (processorID == 1)
    {
        Sample.PEid = processorID;
        //whenPEisGPU(Sample, processorID);
        int i = 0;
        int NoOfConfigs = rand() % 5;
        GPU_data sub_tasks;
        while (i != NoOfConfigs)
        {
            sub_tasks.memory = rand() % 1;
            sub_tasks.number_Copies = rand() % 3+1;
            sub_tasks.workGroups = rand() % 10 + 1;
            sub_tasks.workItems = rand() % (sub_tasks.workGroups * 2) + 1;
            sub_tasks.power_consumption = rand() % 250;
            sub_tasks.execTime = rand() % (int)(Sample.ExecTime / 2);
            Sample.Node_config.push_back(sub_tasks);
            i++;
        }

    }

}

DAG_data PEParameters(DAG_data Sample, int processorID)
{
    if (processorID == 0)
    {
        Sample.ExecTime = rand() % 100;
        Sample.powerDraw = 0.0;
        Sample.noOfCopies = 0;

        return Sample;

    }
    else if (processorID == 1)
    {
        Sample.PEid = processorID;
        //whenPEisGPU(Sample, processorID);
        int i = 0;
        int NoOfConfigs = rand() % 5;
        GPU_data sub_tasks;
        while (i != NoOfConfigs)
        {
            sub_tasks.memory = rand() % 1;
            sub_tasks.number_Copies = rand() % 3 + 1;
            sub_tasks.workGroups = rand() % 10 + 1;
            sub_tasks.workItems = rand() % (sub_tasks.workGroups * 2) + 1;
            sub_tasks.power_consumption = rand() % 250;
            sub_tasks.execTime = rand() % (int)(Sample.ExecTime / 2) + 1;
            Sample.Node_config.push_back(sub_tasks);
            i++;
        }

        return Sample;

    }

}

void generateEdges(std::vector<DAG_data> &myTaskGraph)
{
    unsigned int i = 0;

    while (i != myTaskGraph.size())
    {
        for (unsigned int j = (myTaskGraph[i].processid)+1; j < myTaskGraph.size(); j++)
        {
            if (myTaskGraph[i].follow == 30)
            {
                myTaskGraph[i].follow -= 1;
            }
            //create an edge between the current node and any of its following nodes according to the following random number 
            if (rand() % 100 < 30)
            {
                myTaskGraph[i].follow = j;              
                break;
            }           
        }
        i++;
    }
}

int main()
{
        
    DAG_data emptyDAG;
    unsigned int i = 0;

    std::ofstream myFile;


    std::vector<DAG_data> All_DAGs;

    while (i != NoOfNodes)
    {
        DAG_data DAG1;

        DAG1.processid = i;
        DAG1.transData = i + 1;
        DAG1.PEid = 0;

        DAG1= PEParameters(DAG1, DAG1.PEid);
        DAG1= EdgeAssignment(DAG1, 10);

        All_DAGs.push_back(DAG1);
        //DAG1.Node_config.clear();

        i++;
    }

    generateEdges(All_DAGs);

    for (int h = 0; h < All_DAGs.size(); h++)
    {
        if (h % 2 != 0)
        {
            DAG_data forNewPE =PEParameters(All_DAGs[h], 1);

            All_DAGs.push_back(forNewPE);
            All_DAGs[h].Node_config.clear();
            if (All_DAGs[h].processid ==29)
            {
                break;
            }
        }
    
    }

    myFile.open("TG_Data_30NewEdges.txt");

    for (int i = 0; i < All_DAGs.size(); i++)
    {
        myFile << "Node id: " << All_DAGs[i].processid << std::endl;
        myFile << "Following Edge: " << All_DAGs[i].follow << std::endl;
        myFile << "Transfer Data: " << All_DAGs[i].transData << std::endl;
        myFile << "Node PE: " << All_DAGs[i].PEid << std::endl;
        if (All_DAGs[i].PEid == 0)
        {
            myFile << "Execution time: " << All_DAGs[i].ExecTime << std::endl;
        }
        else
        {
            myFile << "-------------------------------" << std::endl;
            for (int j = 0; j < All_DAGs[i].Node_config.size(); j++)
            {
                myFile << "Execution time: " << All_DAGs[i].Node_config[j].execTime << std::endl;
                myFile << "Copies: " << All_DAGs[i].Node_config[j].number_Copies << std::endl;
                myFile << "Memory: " << All_DAGs[i].Node_config[j].memory << std::endl;
                myFile << "Work-Items: " << All_DAGs[i].Node_config[j].workItems << std::endl;
                myFile << "Work-Groups: " << All_DAGs[i].Node_config[j].workGroups << std::endl;
                myFile << "Power: " << All_DAGs[i].Node_config[j].power_consumption << std::endl;   
                myFile << "++++++++++++++++++" << std::endl;
            }
        }
        myFile << "=================" << std::endl;
    }
    
    myFile.close();

    std::cout << "DONE NOW." << std::endl;

    std::cin.get();

}

El código cumplió su objetivo para mí, pero hay mucho margen de mejora para este código. Indique cómo se puede reescribir este código para cumplir mejor con las prácticas C ++ deseadas.

Respuestas

9 sehe Jan 13 2021 at 02:34

Errores importantes:

  • tu azar no es aleatorio (siembra)

  • su aleatorio no es uniforme (use distribuciones uniformes en lugar de simplemente tomar el módulo, lo que sesgará la distribución)

  • precedea menudo no está inicializado; NoOfConfigsa menudo no se inicializa y nunca se utiliza?

  • El último ciclo antes de escribir el archivo de salida modifica la colección mientras se itera :

     for (size_t h = 0; h < nodes.size(); h++) {
         // ...
         nodes.push_back(forNewPE);
    

    Este es un anti-patrón. Solo te salgas con la tuya por

         if (nodes[h].processid == 29) { break; }
    

    que, por supuesto, sufre de números mágicos, y podría haberse puesto fácilmente en la condición de bucle en su lugar:

     for (size_t h = 0; h < NoOfNodes; ++h) {
    
  • void PESpecificParameters(DAG_data Sample, int processorID) no se utiliza.

    Cuando se usa, nunca tendrá ningún efecto (porque no tiene valores de retorno ni contiene referencias a nada externo)

  • Lo mismo con whenPEisGPU

  • Después de eliminar el código duplicado, parece que PEParametersera idéntico a PESpecificParameters(ver más abajo)

  • Del mismo modo CreateandAssignEdges, ¿no se utilizó y parece que se está duplicando EdgeAssignment?

Notas principales:

  • ¡Nombrar! DAG_Datasignifica casi nada. Su modelo gráfico representa algo en la vida real. El hecho de que sea un DAG es como llamar a las variables "cadena de texto" en lugar de "FirstName" y "ZipCode"

  • Extrae algunas funciones. Úselos para

    • responsabilidades separadas,
    • niveles de abstracción
    • reducir la duplicación
  • Opcionalmente, agrupe funciones relacionadas con sus datos en clases (consulte la sección "BONUS" a continuación)


Aquí viene un golpe a golpe de las cosas que abordé:

  1. Utilice advertencias (-Wall -Wextra -pedantic como mínimo) y golpéelos:

    test.cpp:43:18: warning: unused variable ‘i’ [-Wunused-variable]
       43 |     unsigned int i = 0;
    test.cpp:74:18: warning: unused variable ‘i’ [-Wunused-variable]
       74 |     unsigned int i = 0;
    test.cpp:119:39: warning: unused parameter ‘processorID’ [-Wunused-parameter]
      119 | void whenPEisGPU(DAG_data Sample, int processorID)
    test.cpp:259:23: warning: comparison of integer expressions of different signedness: ‘int’ and ‘std::vector<DAG_data>::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
      259 |     for (int h = 0; h < All_DAGs.size(); h++)
    test.cpp:277:23: warning: comparison of integer expressions of different signedness: ‘int’ and ‘std::vector<DAG_data>::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
      277 |     for (int i = 0; i < All_DAGs.size(); i++)
    test.cpp:290:31: warning: comparison of integer expressions of different signedness: ‘int’ and ‘std::vector<GPU_data>::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
      290 |             for (int j = 0; j < All_DAGs[i].Node_config.size(); j++)
    test.cpp:204:1: warning: control reaches end of non-void function [-Wreturn-type]
      204 | }
    

    Cambios:

    CreateandAssignEdges:
    -    unsigned int i = 0;
    
    EdgeAssignment:
    -    unsigned int i = 0;
    
    -void whenPEisGPU(DAG_data Sample, int processorID)
    +void whenPEisGPU(DAG_data Sample, int /*processorID*/)
    
    PEParameters:
    +    throw std::range_error("processorID");
    
    -    for (int h = 0; h < All_DAGs.size(); h++)
    +    for (size_t h = 0; h < All_DAGs.size(); h++)
    
    -    for (int i = 0; i < All_DAGs.size(); i++)
    +    for (size_t i = 0; i < All_DAGs.size(); i++)
    
    -            for (int j = 0; j < All_DAGs[i].Node_config.size(); j++)
    +            for (size_t j = 0; j < All_DAGs[i].Node_config.size(); j++)
    
  2. La ejecución de la comprobación de modernización / legibilidad muestra muchas advertencias de números mágicos y algunas mejoras sencillas:

    clang-apply-replacements version 9.0.0
    clang-tidy-9 -header-filter=.* -checks=-*,readability-*,modernize-*,-modernize-use-trailing-return-type -export-fixes /tmp/tmp6CfbSr/tmpYGk6CX.yaml -p=/home/sehe/Projects/stackoverflow /home/sehe/Projects/stackoverflow/test.cpp
    /home/sehe/Projects/stackoverflow/test.cpp:59:66: warning: 29 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            Sample.follow = (Sample.processid + 1) + (std::rand() % (29 - (Sample.processid) + 1));
                                                                     ^
    /home/sehe/Projects/stackoverflow/test.cpp:61:30: warning: 30 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            if (Sample.follow == 30)
                                 ^
    /home/sehe/Projects/stackoverflow/test.cpp:81:5: warning: do not use 'else' after 'return' [readability-else-after-return]
        else if (Sample.processid == NoOfNodes - 1)
        ^~~~~
    /home/sehe/Projects/stackoverflow/test.cpp:92:66: warning: 29 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            Sample.follow = (Sample.processid + 1) + (std::rand() % (29 - (Sample.processid) + 1));
                                                                     ^
    /home/sehe/Projects/stackoverflow/test.cpp:119:32: warning: 5 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
        int NoOfConfigs = rand() % 5;
                                   ^
    /home/sehe/Projects/stackoverflow/test.cpp:123:29: warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]
            sub_tasks->memory = rand() % 1;
                                ^
                                ((        ) != 0)
    /home/sehe/Projects/stackoverflow/test.cpp:125:42: warning: 10 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            sub_tasks->workGroups = rand() % 10 +1;
                                             ^
    /home/sehe/Projects/stackoverflow/test.cpp:127:49: warning: 250 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            sub_tasks->power_consumption = rand() % 250;
                                                    ^
    /home/sehe/Projects/stackoverflow/test.cpp:138:36: warning: 100 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            Sample.ExecTime = rand() % 100;
                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:148:36: warning: 5 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            int NoOfConfigs = rand() % 5;
                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:152:32: warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]
                sub_tasks.memory = rand() % 1;
                                   ^
                                   ((        ) != 0)
    /home/sehe/Projects/stackoverflow/test.cpp:154:45: warning: 10 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                sub_tasks.workGroups = rand() % 10 + 1;
                                                ^
    /home/sehe/Projects/stackoverflow/test.cpp:156:52: warning: 250 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                sub_tasks.power_consumption = rand() % 250;
                                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:170:36: warning: 100 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            Sample.ExecTime = rand() % 100;
                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:177:5: warning: do not use 'else' after 'return' [readability-else-after-return]
        else if (processorID == 1)
        ^~~~~
    /home/sehe/Projects/stackoverflow/test.cpp:182:36: warning: 5 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            int NoOfConfigs = rand() % 5;
                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:186:32: warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]
                sub_tasks.memory = rand() % 1;
                                   ^
                                   ((        ) != 0)
    /home/sehe/Projects/stackoverflow/test.cpp:188:45: warning: 10 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                sub_tasks.workGroups = rand() % 10 + 1;
                                                ^
    /home/sehe/Projects/stackoverflow/test.cpp:190:52: warning: 250 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                sub_tasks.power_consumption = rand() % 250;
                                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:211:42: warning: 30 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                if (myTaskGraph[i].follow == 30)
                                             ^
    /home/sehe/Projects/stackoverflow/test.cpp:216:26: warning: 100 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                if (rand() % 100 < 30)
                             ^
    /home/sehe/Projects/stackoverflow/test.cpp:216:32: warning: 30 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                if (rand() % 100 < 30)
                                   ^
    /home/sehe/Projects/stackoverflow/test.cpp:246:36: warning: 10 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
            DAG1= EdgeAssignment(DAG1, 10);
                                       ^
    /home/sehe/Projects/stackoverflow/test.cpp:264:41: warning: 29 is a magic number; consider replacing it with a named constant [readability-magic-numbers]
                if (All_DAGs[h].processid ==29)
                                            ^
    /home/sehe/Projects/stackoverflow/test.cpp:274:5: warning: use range-based for loop instead [modernize-loop-convert]
        for (size_t i = 0; i < All_DAGs.size(); i++)
        ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            (auto & All_DAG : All_DAGs)
    7510 warnings generated.
    Suppressed 7485 warnings (7485 in non-user code).
    Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
    Applying fixes ...
    

    Al menos tome el bucle de rango de inmediato:

    for (auto& DAG : All_DAGs)
    {
        myFile << "Node id: "        << DAG.processid << std::endl;
        myFile << "Following Edge: " << DAG.follow    << std::endl;
        myFile << "Transfer Data: "  << DAG.transData << std::endl;
        myFile << "Node PE: "        << DAG.PEid      << std::endl;
        if (DAG.PEid == 0)
        {
            myFile << "Execution time: " << DAG.ExecTime << std::endl;
        }
        else
        {
            myFile << "-------------------------------" << std::endl;
            for (auto& cfg : DAG.Node_config)
            {
                myFile << "Execution time: " << cfg.execTime          << std::endl;
                myFile << "Copies: "         << cfg.number_Copies     << std::endl;
                myFile << "Memory: "         << cfg.memory            << std::endl;
                myFile << "Work-Items: "     << cfg.workItems         << std::endl;
                myFile << "Work-Groups: "    << cfg.workGroups        << std::endl;
                myFile << "Power: "          << cfg.power_consumption << std::endl;
                myFile << "++++++++++++++++++" << std::endl;
            }
        }
        myFile << "=================" << std::endl;
    }
    
  3. No separe innecesariamente la inicialización de la declaración.

    std::ofstream myFile;
    // 40 lines...
    myFile.open("TG_Data_30NewEdges.txt");
    

    No administre recursos innecesariamente manualmente:

    myFile.close();
    

    El patrón RAII de C ++ significa que el archivo siempre estará cerrado.

    {
        std::ofstream output("TG_Data_30NewEdges.txt");
    
        for (auto& DAG : All_DAGs)
        {
            // ...
        }
    }
    

    Tenga en cuenta que también cambié el nombre myFilea algo más descriptivo.

  4. Es hora de extraer algunas funciones para lo anterior:

    std::ofstream output("TG_Data_30NewEdges.txt");
    writeReport(output, All_DAGs);
    

    Y luego en otro lugar:

    using DAGs = std::vector<DAG_data>;
    
    void writeReport(std::ostream& output, DAGs const& graphs) {
        for (auto& g : graphs) {
            // ...
        }
    }
    
  5. Desmitificar bucles

    unsigned int i = 0;
    while (i != myTaskGraph.size()) {
        // ...
        i++;
    }
    

    Está escrito convencionalmente como

    for (size_t i = 0; i < myTaskGraph.size(); ++i) {
        // ...
    }
    

    O, desde c ++ 0x:

    for (Node& node : myTaskGraph) {
        // ...
    }
    
  6. Del mismo modo, los bucles que construyen contenedores probablemente deberían leerse más como:

    Nodes nodes(NoOfNodes);
    
    size_t i = 0;
    for (auto& current : nodes) {
        current.processid = i;
        current.transData = i + 1;
        current.PEid = 0;
    
        i++;
    
        current = PEParameters(current, current.PEid);
        current = EdgeAssignment(current, 10);
    }
    

    Y

    void whenPEisGPU(Node& node, int /*processorID*/)
    {
        int NoOfConfigs = rand() % 5;
        node.Node_config.assign(NoOfConfigs, {});
    
        for (auto& sub_task : node.Node_config) {
            sub_task.memory            = ((rand() % 1) != 0);
            sub_task.number_Copies     = rand() % 3;
            sub_task.workGroups        = rand() % 10 +1;
            sub_task.workItems         = rand() % (sub_task.workGroups * 2) + 1;
            sub_task.power_consumption = rand() % 250;
            sub_task.execTime          = rand() % (int)(node.ExecTime / 2);
        }
    }
    

    etc.

    Probablemente las escribiría como std::generate_nllamadas en la vida real, pero tal vez lleguemos allí naturalmente, más adelante a continuación.

  7. Nombrar. En algún lugar a la mitad del código, de repente tenemos un vistazo de lo que realmente estamos tratando:

    void generateEdges(std::vector<DAG_data> &myTaskGraph)
    

    Entonces, supongo que podríamos nombrar DAG_data Nodeo Task(¿o incluso TaskNode?).

    Asimismo, obtenemos sugerencias sutiles aquí:

    if (Sample.processid == 0) {
        //parent process- so there will be no edges
    

    y

    else if (node.processid == NoOfNodes - 1) {
        // sink process- so there will be no following edges
    

    Nota al margen: se usa parentcomo si significara "sin bordes". Lo cual es demostrativo inexacto, ya que de inmediato qué define un borde seguidor. Lo que parece querer decir es el "padre sin padre", que en un DAG generalmente se conoce como "raíz". Tenga en cuenta también que si tiene un DAG con solo 1 raíz, ¿por qué no llamarlo Árbol?

    // archivo bajo: el nombre es importante

    Entonces, deberíamos hacerlo más legible:

    using ProcessID = int;
    static constexpr size_t NoOfNodes = 30;
    static constexpr ProcessID RootNodeId = 0;
    static constexpr ProcessID SinkNodeId = NoOfNodes - 1;
    
    // ...
    static bool constexpr IsSink(ProcessID id) { return SinkNodeId == id; }
    static bool constexpr IsSink(Node const& node) { return IsSink(node.processid); }
    // etc?
    
  8. De hecho, tal vez sea mejor combinar todo:

    enum ProcessID : int {
        RootNodeId = 0,
        NoOfNodes  = 30,
        SinkNodeId = NoOfNodes -1,
    };
    

    Esto conduce a una gran reducción de todos los números mágicos (se = 0convierte en = RootNodeIdetc.).

    Sin embargo, nos obliga a abordar el problema con las otras asignaciones "mágicas":

    node.follow = rand() % NoOfEdges + 1;
    node.follow =
        (node.processid + 1) + (std::rand() % (29 - (node.processid) + 1));
    

    Quiero decir, íbamos a abordar esas formas (porque, uf y sesgado al azar).

  9. Entonces, ¡abordemos al azar! Empezaste correctamente:

    #include <random>
    

    ¡pero nunca usé nada de ese tesoro !

    std::mt19937 prng { std::random_device{} () };
    

    ¡Ahora tenemos nuestro UniformRandomBitGenerator y lo sembramos de forma segura!

    Creemos algunas funciones auxiliares que nos ayudarán a generar números distribuidos uniformemente:

    Genere números hasta el máximo incluido:

    auto gen_number(int max, bool includeZero = true) {
        using Dist  = std::uniform_int_distribution<>;
        using Param = Dist::param_type;
        static Dist dist;
    
        auto min = includeZero? 0:1;
        assert(max >= min);
        return dist(prng, Param(min, max));
    }
    

    Añadiendo una mano corta para [1, max] muestra aleatoria:

    auto gen_positive(int max) {
        return gen_number(max, false);
    }
    

    Ahora, para generar ProcessID necesitamos algunas conversiones y podemos asumir algunos valores predeterminados para los límites de rango:

    ProcessID gen_follower(int from = FirstFollow, int to = NoOfNodes) {
        using T     = std::underlying_type_t<ProcessID>;
        using Dist  = std::uniform_int_distribution<T>;
        using Param = Dist::param_type;
    
        static Param full{static_cast<T>(FirstFollow), static_cast<T>(NoOfNodes)};
        static Dist dist(full);
    
        return static_cast<ProcessID>(dist(prng, Param(from, to)));
    }
    

    Ahora podemos reformular las expresiones:

    // node.follow = rand() % NoOfEdges + 1;
    node.follow = gen_follower(FirstFollow, NoOfEdges);
    

    Y

    // node.follow =
    //     (node.processid + 1) + (std::rand() % (29 - (node.processid) + 1));
    node.follow = gen_follower(node.processid+1);
    

    ¡Mucho más simple, seguro y uniforme!

    Ahora, hay algunas cosas raras sobre esto.

    • En todas partes followse implica ser del ProcessIddominio. Sin embargo, la expresión gen_follower(FirstFollow, NoOfEdges)usa en NoOfEdgeslugar de NoOfNodes?! NoOfEdgestambién está codificado en 10para la única llamada a EdgeAssignment.

      ¿Está seguro de que pretendía limitar "arbitrariamente" los nodos seguidores del nodo raíz [1..10]independientemente de NoOfNodes?

      Dado que los seguidores subsiguientes siempre se toman "en sentido descendente", puedo suponer que deseaba elegir de una partición de las "primeras 10" solo para aumentar la probabilidad de que las subtareas engendren "nietos". Si es así, el nombre NoOfEdgeses completamente engañoso y podría ser algo como FirstGenerationNodes?)

    • Hay dos ubicaciones donde se corrige el resultado de estas expresiones:

       if (myTaskGraph[i].follow == 30) {
           myTaskGraph[i].follow -= 1;
       }
      
       if (Sample.follow == 30) {
           Sample.follow -= 1;
       }
      

      Si ese es el rango deseado, ¡simplemente corrija sus expresiones!

      Tal como está escrito, hace que el código sea difícil de entender, distribuye la responsabilidad entre las funciones (lo que invita a errores) y también sesga aún más la distribución: 29ahora es un objetivo de borde mucho más probable.

      Elegí arreglar la expresión para que coincida con la intención implícita de este otro comentario:

      // which nodes will the edges connect to (Anywhere from among the
      // following nodes, including the sink node)
      node.follow = gen_follower(node.processid+1, SinkNodeId);
      
  10. Duplicación de código. La generación de subtareas ( node.Node_config) está duplicada, con algunas diferencias falsas que pueden ser errores, pero ¿podrían ser intencionales?

    P.ej:

    sub_task.number_Copies = rand() % 3 + 1;
    

    Se omite una de las tres copias, lo +1que probablemente sea un error.

    De manera similar vemos una copia de

    sub_task.execTime = rand() % static_cast<int>(node.ExecTime / 2);
    

    eso agrega un +1. Probablemente esto evita cero execTime, y es un olor a código que esto también debería haber sido una distribución aleatoria real uniforme y de tipo fuerte.

    Es difícil adivinar lo que realmente quieres execTimedecir. Si quiere que sea tal que el tiempo de ejecución del nodo padre siempre totalice la suma de sus subtareas, eso es mucho más fácil de expresar con algo de lógica empresarial, en lugar de tener los datos redundantes en su estructura de datos y agregar invariantes indocumentados (que, nuevamente, invitan a errores ).

    Por diversión, agregué cómo escribiría la distribución por capricho:

        void distributeExecTime(Node& node) {
            std::vector<double> weights;
            std::uniform_real_distribution<> dist;
            std::generate_n(
                back_inserter(weights),
                node.Node_config.size(),
                [&dist] { return dist(prng); });
    
            auto total_w = std::accumulate(begin(weights), end(weights), 0.);
    
            for (size_t i = 0; i < weights.size(); ++i) {
                node.Node_config[i].execTime = (weights[i]/total_w) * node.ExecTime;
            }
        }
    
  11. Para el consumo total de energía, parece que suceden cosas similares. Quizás puedas reemplazar powerDraw con una función:

    double powerDraw() const {
        return std::accumulate(begin(Node_config), end(Node_config), 0.);
    };
    

PRIMA

Yendo al límite, podemos imaginar un mundo donde la generación es "automática", como lo es el informe:

  • Considere trasladar la generación a constructores:

     struct GPU_data {
         int number_Copies        = gen_positive(3);
         int workGroups           = gen_positive(10); // order is important!
         int workItems            = gen_positive(workGroups * 2);
         bool memory              = odds(50);
         double power_consumption = gen_real(249);
         double execTime          = 0; // see distributeExecTime
     };
    

    Nota

    • estamos usando C ++ 11 NSMI para generar el constructor predeterminado para nosotros
     struct Node {
         enum Type { CPUNode, GPUNode };
    
         Type      PEid;      // Processor's ID to which node is assigned
         ProcessID processid; // Node's ID
         Configs   sub_tasks;
         ProcessID follow    = RootNodeId; // nodes following this node
         double    transData = 0;
         double    ExecTime  = 0;
    
         explicit Node(int id, int NoOfEdges = 10)
           : PEid(CPUNode),
             processid(ProcessID(id)),
             transData(id + 1)
         {
             PEParameters();
             EdgeAssignment(NoOfEdges);
         }
    
         explicit Node(Node const& node)
          : PEid(GPUNode),
            processid(node.processid),
            sub_tasks(),
            follow(node.follow),
            transData(node.transData),
            ExecTime(node.ExecTime)
         {
             PEParameters();
         }
    
         double powerDraw() const;
         bool isGPU() const { return PEid == GPUNode; }
    
       private:
         void PEParameters();
         void EdgeAssignment(int NoOfEdges);
         void distributeExecTime();
     };
    

    Ahora, Nodepuede agrupar con sus funciones de manipulación:

    • Este tipo de asume que los tipos no están ya en uso en otros lugares. En caso de que ese no sea el caso, podemos subclasificar los tipos y beneficiarnos de la división de objetos para convertirlos de nuevo a su clase base.

    • Tenga en cuenta también que varios lugares en el código (PEParameters, output y EdgeAssignment) cambian el comportamiento en PEid que aparentemente solo tiene dos valores válidos. Lo cambié para que sea una enumeración que refleje ese hecho:

      enum Type { CPUNode, GPUNode };
      Type      PEid;      // Processor's ID to which node is assigned
      

      Como ejercicio para el lector, podría tener sentido cambiar Nodea algún tipo de tipo polimórfico en lugar de cambiar todo el tiempo:

      using Node = std::variant<CPUNode, GPUNode>;
      

      O usando tipos virtuales (herencia).

Listado (s) de demostración

Todas las revisiones están aquí en una esencia: https://gist.github.com/sehe/32c07118031a049042bd9fb469355caf/revisions

Vivir en Coliru

#include <iostream>
#include <algorithm>    // std::min_element, std::max_element
#include <fstream>
#include <string>
#include <random>
#include <vector>
#include <cassert>

namespace {
    static std::mt19937 prng { std::random_device{} () };

    enum ProcessID : int {
        RootNodeId /*= 0 */,
        NoOfNodes    = 30,

        FirstFollow  = RootNodeId +1,
        SinkNodeId   = NoOfNodes -1,
    };

    auto gen_number(int max, bool includeZero = true) {
        using Dist  = std::uniform_int_distribution<>;
        using Param = Dist::param_type;
        static Dist dist;

        auto min = includeZero? 0:1;
        assert(max >= min);
        return dist(prng, Param(min, max));
    }

    auto gen_positive(int max) {
        return gen_number(max, false);
    }

    ProcessID gen_follower(int from = FirstFollow, int to = NoOfNodes) {
        using T     = std::underlying_type_t<ProcessID>;
        using Dist  = std::uniform_int_distribution<T>;
        using Param = Dist::param_type;

        static Param full{static_cast<T>(FirstFollow), static_cast<T>(NoOfNodes)};
        static Dist dist(full);

        return static_cast<ProcessID>(dist(prng, Param(from, to)));
    }

    bool odds(int percentage) {
        if (percentage == 100)
            return true;
        assert(percentage > 0 && percentage < 100);
        return std::discrete_distribution<bool>(percentage, 100-percentage)(prng);
    }

    double gen_real(double mean = 100.0, double stddev = 0) {
        if (stddev == 0)
            stddev = mean/4;
        assert(stddev>0);
        return std::normal_distribution(mean, stddev)(prng);
    }
}

struct GPU_data {
    int number_Copies        = gen_positive(3);
    int workGroups           = gen_positive(10); // order is important!
    int workItems            = gen_positive(workGroups * 2);
    bool memory              = odds(50);
    double power_consumption = gen_real(249);
    double execTime          = 0; // see distributeExecTime
};

using Configs = std::vector<GPU_data>;

struct Node {
    enum Type { CPUNode, GPUNode };

    Type      PEid;      // Processor's ID to which node is assigned
    ProcessID processid; // Node's      ID
    Configs   sub_tasks;
    ProcessID follow    = RootNodeId; // nodes following this node
    double    transData = 0;
    double    ExecTime  = 0;

    explicit Node(int id, int NoOfEdges = 10)
      : PEid(CPUNode),
        processid(ProcessID(id)),
        transData(id + 1)
    {
        PEParameters();
        EdgeAssignment(NoOfEdges);
    }

    explicit Node(Node const& node)
     : PEid(GPUNode),
       processid(node.processid),
       sub_tasks(),
       follow(node.follow),
       transData(node.transData),
       ExecTime(node.ExecTime)
    {
        PEParameters();
    }

    double powerDraw() const {
        double total = 0;
        for (auto& sub: sub_tasks) {
            total += sub.power_consumption;
        }
        return total;
    };

    bool isGPU() const { return PEid == GPUNode; }

  private:
    void PEParameters() {
        switch(PEid) {
          case CPUNode:
            ExecTime = gen_real(100.0);
            break;
          case GPUNode:
            sub_tasks.resize(gen_number(5));
            distributeExecTime();
            break;
          default:
            throw std::range_error("PEid");
        }
    }

    void EdgeAssignment(int NoOfEdges) {
        if (processid == RootNodeId) {
            // parent process- so there will be no edges
            follow  = gen_follower(FirstFollow, NoOfEdges);
        }
        else if (processid == SinkNodeId) {
            // sink process- so there will be no following edges
            follow = RootNodeId;
        }
        else {
            // which nodes will the edges connect to (Anywhere from among the
            // following nodes, including the sink node)
            follow = gen_follower(processid+1, SinkNodeId);
        }
    }

    void distributeExecTime() {
        std::vector<double> weights;
        std::uniform_real_distribution<> dist;
        std::generate_n(
            back_inserter(weights),
            sub_tasks.size(),
            [&dist] { return dist(prng); });

        auto total_w = std::accumulate(begin(weights), end(weights), 0.);

        for (size_t i = 0; i < weights.size(); ++i) {
            sub_tasks[i].execTime = (weights[i]/total_w) * ExecTime;
        }
    }
};

using Nodes = std::vector<Node>;

void generateEdges(Nodes& nodes) {
    for (Node& node : nodes) {
        // Create an edges to following nodes given 30% odds
        for (size_t j = node.processid+1; j < nodes.size(); j++) {
            if (odds(30)) {
                node.follow = static_cast<ProcessID>(j);              
                break;
            }           
        }
    }
}

static std::ostream& operator<<(std::ostream& os, Node const& n);

int main() {
    Nodes nodes;
    for (auto id = 0; id < NoOfNodes; ++id) {
        nodes.emplace_back(id);
    }

    generateEdges(nodes);

    for (size_t h = 0; h < NoOfNodes; h++) {
        if (h % 2 == 0)
            continue;

        nodes.emplace_back(nodes[h]);
        nodes[h].sub_tasks.clear();
    }

    std::ofstream output("TG_Data_30NewEdges.txt");
    for (auto& n : nodes) {
        output << n << "=================\n";
    }

    std::cout << "DONE" << std::endl;
}

static std::ostream& operator<<(std::ostream& os, GPU_data const& cfg) {
    return os 
        << "Execution time: " << cfg.execTime          << "\n"
        << "Copies: "         << cfg.number_Copies     << "\n"
        << "Memory: "         << cfg.memory            << "\n"
        << "Work-Items: "     << cfg.workItems         << "\n"
        << "Work-Groups: "    << cfg.workGroups        << "\n"
        << "Power: "          << cfg.power_consumption << "\n";
}

static std::ostream& operator<<(std::ostream& os, Node const& n) {
    os << "Node id: "        << n.processid   << "\n"
       << "Following Edge: " << n.follow      << "\n"
       << "Transfer Data: "  << n.transData   << "\n"
       << "Node powerDraw: " << n.powerDraw() << "\n"
       << "Node PE: "        << n.PEid        << "\n";

    if (n.isGPU()) {
        os << "-------------------------------\n";
        for (auto& cfg : n.sub_tasks) {
            os << cfg << "++++++++++++++++++\n";
        }
    } else {
        os << "Execution time: " << n.ExecTime << "\n";
    }
    return os;
}

Impresiones, p. Ej.

DONE

Y genera TG_Data_30NewEdges.txt:

Node id: 0
Following Edge: 1
Transfer Data: 1
Node powerDraw: 1020.61
Node PE: 1
-------------------------------
Execution time: 12.2428
Copies: 1
Memory: 1
Work-Items: 10
Work-Groups: 9
Power: 229.989
++++++++++++++++++
Execution time: 39.2756
Copies: 1

// ...
// 825 lines snipped
// ...

Copies: 3
Memory: 1
Work-Items: 3
Work-Groups: 9
Power: 235.512
++++++++++++++++++
=================
1 TobySpeight Jan 11 2021 at 18:32
#define NoOfNodes 30

Creo que sería mejor utilizar una static constexprmacro aquí en lugar de una macro de preprocesador.

    //which nodes will the edges connect to (Anywhere from among the following nodes, including the sink node)
    Sample.follow = (Sample.processid + 1) + (std::rand() % (29 - (Sample.processid) + 1));

    if (Sample.follow == 30)
    {
        Sample.follow -= 1;
    }

¿De dónde vienen las constantes 29y 30? ¿Deberían derivarse de ellos en su NoOfNodeslugar?

Puede ser mejor utilizar la <random>biblioteca C ++ que std::rand().

CreateandAssignEdges()y EdgeAssignment()son muy similares; creo que la duplicación se puede reducir considerablemente.

   //Sample->precede = rand() % NoOfEdges;
    //Sample->follow = rand() % NoOfEdges;

    ////Preceding and following edges of a node should not be the same.
    //while (Sample->precede > Sample->follow || Sample->precede == Sample->follow)
    //{
    //  //assign both edges again
    //  Sample->follow = rand() % NoOfEdges;
    //  Sample->precede = rand() % NoOfEdges;
    //}

Los fragmentos de código comentado como este a menudo se convierten en un problema, quedando desactualizados e inconsistentes a medida que cambia el código circundante. Elimínelo o encuentre una manera de asegurarse de que se compile y se pruebe unitariamente con el resto del código.

    myFile << "Node id: " << All_DAGs[i].processid << std::endl;
    myFile << "Following Edge: " << All_DAGs[i].follow << std::endl;
    myFile << "Transfer Data: " << All_DAGs[i].transData << std::endl;
    myFile << "Node PE: " << All_DAGs[i].PEid << std::endl;

No hay una necesidad real de vaciar myFilecada declaración; prefiera '\n'hacerlo std::endlpara todos estos (y la mayoría / todos los usos restantes).