Search code examples
databasegoperformance-testing

Golang code running really slow than same code in PHP


New to Golang, Yesterday I've started to play with Golang and wrote some code which was actually written in PHP. I just wanted to see difference in performance.

I am doing the exact same thing in PHP response is exact same in http request but the Golang is performing really slow even after compiling it.

I am trying to understand what things that I am using in Golang I shouldn't be using and how can I improve performance in this piece of Code.

I know Iterating over map is slow but PHP using hash maps for implementing multidimentional arrays, well. I can gurantee the sql queries I used were exact same copy pasted from PHP, machines are same, and loop numbers are same in both codes.

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "net/http"
    "reflect"
    "strings"
)

func main() {
    db, err := sql.Open("mysql", "***:****@tcp(****:3306)/****")
    fmt.Println(reflect.TypeOf(db))
    checkErr(err)
    fmt.Println("Handle Request setup... OK")
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {

        jsonData, err := getListings(db)
        checkErr(err)

        w.Write([]byte(jsonData))

    })
    fmt.Println("Starting Server....")
    fmt.Println("Listening on port 8081")
    http.ListenAndServe(":8081", nil)

}

func getListings(db *sql.DB) ([]byte, error) {
    var userId string = "142"

    normalListings := sqlToArray(db, `******`)

    manualListings := sqlToArray(db, "******")

    var groupIds []string
    for key := range manualListings {

        groupId := "142," + manualListings[key]["group_id"]
        if !stringInSlice(groupId, groupIds) {
            groupIds = append(groupIds, groupId)
        }
    }

    var groupIdsString string
    groupIdsString = strings.Join(groupIds, ", ")

    manualGroups := sqlToArray(db, "*****")

    for key := range manualListings {

        for key2 := range manualGroups {
            groupId := "142," + manualListings[key]["group_id"]

            if groupId == manualGroups[key]["ticket_id"] {
                entry := make(map[string]string)
                entry["ticket_id"] = manualListings[key]["listing_id"]
                entry["date_created"] = manualGroups[key2]["date_created"]
                normalListings = append(normalListings, entry)

            }
        }
    }

    return json.Marshal(normalListings)

}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func sqlToArray(db *sql.DB, sqlString string) []map[string]string {

    rows, err := db.Query(sqlString)
    checkErr(err)
    columns, err := rows.Columns()
    count := len(columns)
    values := make([]interface{}, count)
    valuePtrs := make([]interface{}, count)
    tableData := make([]map[string]string, 0)

    for rows.Next() {

        for i := 0; i < count; i++ {
            valuePtrs[i] = &values[i]
        }
        rows.Scan(valuePtrs...)
        entry := make(map[string]string)
        for i, col := range columns {

            val := values[i]
            b, ok := val.([]byte)
            if ok {
                entry[col] = string(b)
            } else {
                entry[col] = string(b)
            }

        }
        tableData = append(tableData, entry)

    }

    return tableData

}

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

Edits: Changed the code to use statically typed structs instead of using maps and Identified the problematic piece of code

New code:

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "net/http"
    "strings"
)

type listingsType struct {
    TicketId    string
    DateCreated string
}

func main() {
    db, err := sql.Open("mysql", "******")

    checkErr(err)
    fmt.Println("Handle Request setup... OK")
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {

        jsonData, err := getListings(db)
        checkErr(err)

        w.Write([]byte(jsonData))

    })
    fmt.Println("Starting Server....")
    fmt.Println("Listening on port 8081")
    http.ListenAndServe(":8081", nil)

}

func getListings(db *sql.DB) ([]byte, error) {
    var userId string = "142"

    normalListings := sqlToArray(db, `*****`)

    manualListings := sqlToArray(db, "*****")

    var groupIds []string

    for _, elem := range manualListings {
        groupId := "142," + elem.DateCreated
        if !stringInSlice(groupId, groupIds) {
            groupIds = append(groupIds, groupId)
        }

    }

    var groupIdsString string
    groupIdsString = strings.Join(groupIds, ", ")
    fmt.Println(groupIdsString)
    manualGroups := sqlToArray(db, "******")

    for _, manualList := range manualListings {

        for _, manualGroup := range manualGroups {
            groupId := "142," + manualList.DateCreated

            if groupId == manualGroup.TicketId {
                var entry listingsType
                entry.TicketId = manualList.TicketId
                entry.DateCreated = manualGroup.DateCreated
                normalListings = append(normalListings, entry)

            }
        }
    }

    return json.Marshal(normalListings)

}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func sqlToArray(db *sql.DB, sqlString string) []listingsType {

    rows, err := db.Query(sqlString)
    checkErr(err)

    tableData := []listingsType{}

    for rows.Next() {

        var entry listingsType
        rows.Scan(&entry.TicketId, &entry.DateCreated)

        tableData = append(tableData, entry)

    }

    return tableData

}

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

Problematic piece of code As soon as I comment the following block of code the my code performs just fine.

Any idea what is wrong with this loop ?

for _, manualList := range manualListings {

        for _, manualGroup := range manualGroups {
            groupId := "142," + manualList.DateCreated

            if groupId == manualGroup.TicketId {
                var entry listingsType
                entry.TicketId = manualList.TicketId
                entry.DateCreated = manualGroup.DateCreated
                normalListings = append(normalListings, entry)

            }
        }
    }

Profiling Result

enter image description here


Solution

  • Ok So got it fixed by the way. I brought the request time from 5k+ MS to 500 MS, now finally my PHP code is slower which is 900 MS

    I got rid of the inner loop to search by implementing a separate function to get SQL data in a different data structure in key value of maps instead of searching whole arrays I created the value as key which I was looking for in array, This way I got rid of the second loop which was making trouble by linear search on strings.

    manualGroups := sqlToArraySpecial(db, "****")
    
        for _, manualList := range manualListings {
            //index := stringInSliceArray(manualList.DateCreated, manualGroups)
            groupId := "142," + manualList.DateCreated
            var entry listingsType
            entry.TicketId = manualList.TicketId
            entry.DateCreated = manualGroups[groupId]
            normalListings = append(normalListings, entry)
    
        }
    

    and here is my new SQL function

    func sqlToArraySpecial(db *sql.DB, sqlString string) map[string]string {
    
        rows, err := db.Query(sqlString)
        checkErr(err)
    
        tableData := make(map[string]string)
    
        for rows.Next() {
    
            var date_created string
            var ticket_id string
    
            rows.Scan(&ticket_id, &date_created)
            //fmt.Println(ticket_id)
            tableData[ticket_id] = date_created
    
        }
    
        return tableData
    
    }