Search code examples
c++vb.netvisual-c++scientific-computing

Why VisualBasic (VB.NET) faster than C++ code in this example?


I have been benchmarking an iterative calculation, a Lapalace equation solver for electrostatics, that uses the Jacobi method. I wrote the same algorithm in Visual Basic and C++ (and other languages too). For some reason, Visual Basic .Net came as the fastest. I don't understand why. Since C++ is compiled to bytecode, then I would expect it to be faster. I feel that my C++ is perhaps not optimised as it should. Any help understanding why C++ is not faster than VisualBasic would be appreciated. Thank you.

In the following codes, the iteration loop lasts about 2.8 seconds with Visual basic, and VisualC++ takes 4.8 seconds

Visual Basic Code

Imports System.Timers

Module Module1
    Const ARRAYDIM = 500
    Const iterationVMaxError = 0.001

    Sub Main()
        Dim PAArrayPotentials(ARRAYDIM, ARRAYDIM) As Single
        Dim PAArrayIsElectrode(ARRAYDIM, ARRAYDIM) As Boolean

        Console.WriteLine("SpeedTestEMLaplaceSolverVB")
        Console.WriteLine("Start generating electrodes and popuate 2D array")

        'Start generating electrodes
        For iy = 0 To ARRAYDIM - 1
            For ix = 0 To ARRAYDIM - 1
                PAArrayPotentials(iy, ix) = 0 'Default
                PAArrayIsElectrode(iy, ix) = False

                If ix = 20 And (iy > 150 And iy < 350) Then
                    PAArrayPotentials(iy, ix) = 1 'Default
                    PAArrayIsElectrode(iy, ix) = True
                End If

                If ix = 480 And (iy > 150 And iy < 350) Then
                    PAArrayPotentials(iy, ix) = -1 'Default
                    PAArrayIsElectrode(iy, ix) = True
                End If

            Next
        Next

        Console.WriteLine("Finished creating electrodes.")

        Console.WriteLine("Press enter key to start")
        Console.ReadLine()
        Console.WriteLine("Starting iterative Laplace.")

        Dim iteration As Integer = 0
        Dim maxerror As Single = 0
        Dim t0 = System.Diagnostics.Stopwatch.StartNew()

        Do
            maxerror = 0
            For iy = 0 To ARRAYDIM - 1
                For ix = 0 To ARRAYDIM - 1
                    If Not PAArrayIsElectrode(iy, ix) Then
                        Dim sum As Single = 0
                        Dim nvalues As Single = 0

                        If iy > 0 Then
                            sum += PAArrayPotentials(iy - 1, ix)
                            nvalues += 1
                        End If
                        If iy < ARRAYDIM - 1 Then
                            sum += PAArrayPotentials(iy + 1, ix)
                            nvalues += 1
                        End If
                        If ix > 0 Then
                            sum += PAArrayPotentials(iy, ix - 1)
                            nvalues += 1
                        End If
                        If ix < ARRAYDIM - 1 Then
                            sum += PAArrayPotentials(iy, ix + 1)
                            nvalues += 1
                        End If

                        If nvalues > 0 Then
                            Dim newval As Single = sum / nvalues
                            Dim vchange As Single = Math.Abs(newval - PAArrayPotentials(iy, ix))
                            maxerror = Math.Max(vchange, maxerror)

                            PAArrayPotentials(iy, ix) = newval
                        End If
                    End If
                Next
            Next

            iteration += 1

        Loop While maxerror > iterationVMaxError

        Dim deltaT As Long = t0.ElapsedMilliseconds

        Console.WriteLine("Completed, in " + iteration.ToString + " iteration cycles; deltat = " + deltaT.ToString + " miliseconds.")

        Console.WriteLine("Press enter key to close")
        Console.ReadLine()
    End Sub

End Module

Visual C++ code

// SpeedTestEMLaplaceSolver2.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <array>
#include <cmath>
#include <chrono>

#define ARRAYDIM 500

//Use normal arrays rather than the std::array
float PAArrayPotentials[ARRAYDIM][ARRAYDIM];
bool PAArrayIsElectrode[ARRAYDIM][ARRAYDIM];

int main()
{
    std::cout << "SpeedTestEMLaplaceSolver2.cpp using standard arrays float PAArrayPotentials[PAWIDTH][PAHEIGHT];\n";
    std::cout << "500 x 500 with 2 electrodes at position 20 and 480. Height of 200 each.\n" <<
        "Left electrode at + 1 V and right electrode at - 1V.\n" <<
        "iterationVMaxError = 1e-3.\n";

    const float iterationVMaxError = 1e-3f;

    //Fill array with zeros and electrodes
    std::cout << "Start generating electrodes and PA.\n";
    for (int iy = 0; iy < ARRAYDIM; iy++) {
        for (int ix = 0; ix < ARRAYDIM; ix++) {
            float * pmyPointPot = &PAArrayPotentials[iy][ix]; //get a reference to the point
            bool * pmyPointIsEl = &PAArrayIsElectrode[iy][ix]; //get a reference to the point

            //Default for all points
            *pmyPointPot = 0;
            *pmyPointIsEl = false;

            //Electrode
            if (ix == 20 && (iy > 150 && iy < 350 )) {
                *pmyPointPot = 1;
                *pmyPointIsEl = true;
            }
            if (ix == 480  && (iy > 20 && iy < 350 ) ) {
                *pmyPointPot = -1;
                *pmyPointIsEl = true;
            }
        }
    }

    std::cout << "Completed generating electrodes and PA.\n";

    std::cout << "Type a letter and enter key to start calculation.\n";
    char stemp[80];
    std::cin >> stemp;
    std::cout << "Start iterative laplace\n";

    auto starttime = std::chrono::steady_clock::now();
    long iteration = 0;

    float perror;
    //Convergence routine
    do {
        perror = 0; //resets
        
        for (int iy = 0; iy < ARRAYDIM; iy++) {
            for (int ix = 0; ix < ARRAYDIM; ix++) {

                if (!PAArrayIsElectrode[iy][ix]) {
                    //Makes the new value the average of surrounding values
                    float sum = 0;
                    float nvalues = 0;
                    if (iy > 0) {
                        sum += PAArrayPotentials[iy - 1][ix];
                        nvalues++;
                    }
                    if (iy < ARRAYDIM - 1) {
                        sum += PAArrayPotentials[iy + 1][ix];
                        nvalues++;
                    }
                    if (ix > 0) {
                        sum += PAArrayPotentials[iy][ix - 1];
                        nvalues++;
                    }
                    if (ix < ARRAYDIM - 1) {
                        sum += PAArrayPotentials[iy][ix + 1];
                        nvalues++;
                    }
                    if (nvalues > 0) {
                        float newVal = sum / nvalues;
                        float vchange = fabs(newVal - PAArrayPotentials[iy][ix]);
                        perror = fmax(vchange, perror);

                        PAArrayPotentials[iy][ix] = newVal; //Set the new calculated value
                    }
                }
            }
        }
        iteration++;
        //std::cout << "iteration: " << iteration << " , perror= " << perror << std::endl;
    } while (perror > iterationVMaxError );

    auto endtime = std::chrono::steady_clock::now();

    std::cout << "Completed, in " << iteration << " iteration cycles; deltat = " << std::chrono::duration_cast<std::chrono::milliseconds>(endtime - starttime).count() << " miliseconds" << std::endl;

    return 0;
}

Solution

  • Ok. After digging further as to why VB.Net was faster than C++, I hypothesised that the issue could be due to the C++ compiler.

    I downloaded the Intel C++ compiler and recompiled the program. I also changed the array to 1024x1024 size and the criteria to stop the iteration to maxchange=1e-4. All developed under MS Visual Studio 2019 community.

    Benchmarking results are:

    Visual C++ (MS): 66356 miliseconds
    Visual C++ (Intel): 39013 miliseconds
    VB.Net : 62847 miliseconds
    

    So, now C++ compiled code (intel) is faster.

    And here is the code I used

    // SpeedTestEMLaplaceSolver2.cpp : This file contains the 'main' function. Program execution begins and ends there.
    //
    #include <iostream>
    #include <array>
    #include <cmath>
    #include <chrono>
    
    #define ARRAYDIM 1024
    
    //Use normal arrays rather than the std::array
    float PAArrayPotentials[ARRAYDIM][ARRAYDIM];
    bool PAArrayIsElectrode[ARRAYDIM][ARRAYDIM];
    
    int main()
    {
        const float iterationVMaxError = 1e-4f;
    
        std::cout << "SpeedTestEMLaplaceSolver2.cpp using standard arrays float PAArrayPotentials[PAWIDTH][PAHEIGHT];\n";
        std::cout << "Dimensions: " << ARRAYDIM << "x" << ARRAYDIM << " , iterationVMaxError = " << iterationVMaxError << "\n" <<
            "Left electrode at + 1 V and right electrode at - 1V.\n";
    
        const int  pos_1_4 = (int)floor(ARRAYDIM / 4);
        const int  pos_3_4 = (int)floor(ARRAYDIM * 3 / 4);
    
        //Fill array with zeros and electrodes
        std::cout << "Start generating electrodes and PA.\n";
        for (int iy = 0; iy < ARRAYDIM; iy++) {
            for (int ix = 0; ix < ARRAYDIM; ix++) {
                float * pmyPointPot = &PAArrayPotentials[iy][ix]; //get a reference to the point
                bool * pmyPointIsEl = &PAArrayIsElectrode[iy][ix]; //get a reference to the point
    
                //Default for all points
                *pmyPointPot = 0;
                *pmyPointIsEl = false;
    
                //Electrode
                if (ix == pos_1_4 && (iy > pos_1_4 && iy < pos_3_4)) {
                    *pmyPointPot = 1;
                    *pmyPointIsEl = true;
                }
                if (ix == pos_3_4 && (iy > pos_1_4 && iy < pos_3_4) ) {
                    *pmyPointPot = -1;
                    *pmyPointIsEl = true;
                }
            }
        }
    
        std::cout << "Completed generating electrodes and PA.\n";
    
        std::cout << "Type a letter and enter key to start calculation.\n";
        char stemp[80];
        std::cin >> stemp;
        std::cout << "Start iterative laplace\n";
    
        auto starttime = std::chrono::steady_clock::now();
        long iteration = 0;
    
        float maxerror =0;
        //Convergence routine
        do {
            maxerror = 0; //resets
            
            for (int iy = 0; iy < ARRAYDIM; iy++) {
                for (int ix = 0; ix < ARRAYDIM; ix++) {
    
                    if (!PAArrayIsElectrode[iy][ix]) {
                        //Makes the new value the average of surrounding values
                        float sum = 0;
                        float nvalues = 0;
                        if (iy > 0) {
                            sum += PAArrayPotentials[iy - 1][ix];
                            nvalues++;
                        }
                        if (iy < ARRAYDIM - 1) {
                            sum += PAArrayPotentials[iy + 1][ix];
                            nvalues++;
                        }
                        if (ix > 0) {
                            sum += PAArrayPotentials[iy][ix - 1];
                            nvalues++;
                        }
                        if (ix < ARRAYDIM - 1) {
                            sum += PAArrayPotentials[iy][ix + 1];
                            nvalues++;
                        }
                        if (nvalues > 0) {
                            float newVal = sum / nvalues;
                            float vchange = fabs(newVal - PAArrayPotentials[iy][ix]);
                            maxerror = fmax(vchange, maxerror);
    
                            PAArrayPotentials[iy][ix] = newVal; //Set the new calculated value
                        }
                    }
                }
            }
            iteration++;
            //std::cout << "iteration: " << iteration << " , perror= " << perror << std::endl;
        } while (maxerror > iterationVMaxError );
    
        auto endtime = std::chrono::steady_clock::now();
    
        std::cout << "Completed, in " << iteration << " iteration cycles; deltat = " << std::chrono::duration_cast<std::chrono::milliseconds>(endtime - starttime).count() << " miliseconds" << std::endl;
    
        return 0;
    }
    

    And the VB.Net

    Imports System.Timers
    
    Module Module1
        Const ARRAYDIM = 1024
        Const iterationVMaxError = 0.0001 ' 1e-4
    
        Sub Main()
            Dim PAArrayPotentials(ARRAYDIM - 1, ARRAYDIM - 1) As Single
            Dim PAArrayIsElectrode(ARRAYDIM - 1, ARRAYDIM - 1) As Boolean
    
            Console.WriteLine("SpeedTestEMLaplaceSolverVB")
            Console.WriteLine("Dimensions: " + ARRAYDIM.ToString + "x" + ARRAYDIM.ToString + " , iterationVMaxError = " + iterationVMaxError.ToString)
            Console.WriteLine("Start generating electrodes and popuate 2D array")
    
            Dim pos_1_4 As Integer = Int(ARRAYDIM / 4)
            Dim pos_3_4 As Integer = Int(ARRAYDIM * 3 / 4)
    
            'Start generating electrodes
            For iy = 0 To ARRAYDIM - 1
                For ix = 0 To ARRAYDIM - 1
                    PAArrayPotentials(iy, ix) = 0 'Default
                    PAArrayIsElectrode(iy, ix) = False
    
    
                    If ix = pos_1_4 And (iy > pos_1_4 And iy < pos_3_4) Then
                        PAArrayPotentials(iy, ix) = 1 'Default
                        PAArrayIsElectrode(iy, ix) = True
                    End If
    
                    If ix = pos_3_4 And (iy > pos_1_4 And iy < pos_3_4) Then
                        PAArrayPotentials(iy, ix) = -1 'Default
                        PAArrayIsElectrode(iy, ix) = True
                    End If
    
                Next
            Next
    
            Console.WriteLine("Finished creating electrodes.")
    
            Console.WriteLine("Press enter key to start")
            Console.ReadLine()
            Console.WriteLine("Starting iterative Laplace.")
    
            Dim iteration As Integer = 0
            Dim maxerror As Single = 0
            Dim t0 = System.Diagnostics.Stopwatch.StartNew()
    
            Do
                maxerror = 0
                For iy = 0 To ARRAYDIM - 1
                    For ix = 0 To ARRAYDIM - 1
                        If Not PAArrayIsElectrode(iy, ix) Then
                            Dim sum As Single = 0
                            Dim nvalues As Single = 0
    
                            If iy > 0 Then
                                sum += PAArrayPotentials(iy - 1, ix)
                                nvalues += 1
                            End If
                            If iy < ARRAYDIM - 1 Then
                                sum += PAArrayPotentials(iy + 1, ix)
                                nvalues += 1
                            End If
                            If ix > 0 Then
                                sum += PAArrayPotentials(iy, ix - 1)
                                nvalues += 1
                            End If
                            If ix < ARRAYDIM - 1 Then
                                sum += PAArrayPotentials(iy, ix + 1)
                                nvalues += 1
                            End If
    
                            If nvalues > 0 Then
                                Dim newval As Single = sum / nvalues
                                Dim vchange As Single = Math.Abs(newval - PAArrayPotentials(iy, ix))
                                maxerror = Math.Max(vchange, maxerror)
    
                                PAArrayPotentials(iy, ix) = newval
                            End If
                        End If
                    Next
                Next
    
                iteration += 1
    
            Loop While maxerror > iterationVMaxError
    
            Dim deltaT As Long = t0.ElapsedMilliseconds
    
            Console.WriteLine("Completed, in " + iteration.ToString + " iteration cycles; deltat = " + deltaT.ToString + " miliseconds.")
    
            Console.WriteLine("Press enter key to close")
            Console.ReadLine()
        End Sub
    
    End Module