Python 多线程性能 - 改用 C++?

Posted

技术标签:

【中文标题】Python 多线程性能 - 改用 C++?【英文标题】:Python multithreading performance - use C++ instead? 【发布时间】:2014-06-06 16:21:23 【问题描述】:

所以,我有一个 Python 脚本,它基本上可以编写一个超过 80GB 的文件。目前它只是串行运行,我唯一一次实际运行它需要大约 13 个小时才能在服务器上运行。

我将并行化它,以便它写入许多文件,而不仅仅是一个。

将我已经拥有的东西保存在 Python 中会稍微容易一些,但要合并多个线程(他们需要访问一个共享数据的单个映射,没有人会写入,所以它不会需要保护)。

但是,将其保留在 Python 中是否愚蠢?我也知道 C++,所以你认为我应该用 C++ 重写它吗?我认为该程序比其他任何程序都更受磁盘限制(没有用于写入文件的大量逻辑),所以它可能没有太大区别。我不确定 C++ 需要多长时间来编写相同的 80GB 文件(串行)。


2014 年 6 月 6 日更新,太平洋标准时间 16:40: 我在下面发布我的代码以确定代码本身是否存在瓶颈,而不是纯粹的磁盘绑定。

我为每个表调用一次 writeEntriesToSql(),其中大约有 30 个表。 “大小”是要写入表的插入数。所有表的累积大小约为 200,000,000。

我确实注意到我一遍又一遍地编译我的正则表达式,这可能会给我带来很多浪费,尽管我不确定有多少。

def writeEntriesToSql(db, table, size, outputFile):

# get a description of the table
rows = queryDatabaseMultipleRows(db, 'DESC ' + table)
fieldNameCol = 0    # no enums in python 2.7 :(
typeCol = 1
nullCol = 2
keyCol = 3
defaultCol = 4
extraCol = 5

fieldNamesToTypes =   

for row in rows:
    if (row[extraCol].find("auto_increment") == -1):
        # insert this one
        fieldNamesToTypes[row[fieldNameCol]] = row[typeCol]    


for i in range(size):
    fieldNames = ""
    fieldVals = "" 
    count = 0

    # go through the fields
    for fieldName, type in fieldNamesToTypes.iteritems():
            # build a string of field names to be used in the INSERT statement
        fieldNames += table + "." + fieldName

        if fieldName in foreignKeys[table]:
            otherTable = foreignKeys[table][fieldName][0]
            otherTableKey = foreignKeys[table][fieldName][1]
            if len(foreignKeys[table][fieldName]) == 3:
                # we already got the value so we don't have to get it again
                val = foreignKeys[table][fieldName][2]
            else:
                # get the value from the other table and store it
                #### I plan for this to be an infrequent query - unless something is broken here!
                query = "SELECT " + otherTableKey + " FROM " + otherTable + " LIMIT 1"
                val = queryDatabaseSingleRowCol(db, query)
                foreignKeys[table][fieldName].append(val)
            fieldVals += val
        else:
            fieldVals += getDefaultFieldVal(type)
        count = count + 1
        if count != len(fieldNamesToTypes):
            fieldNames += ","
            fieldVals += ","        


# return the default field value for a given field type which will be used to prepopulate our tables
def getDefaultFieldVal(type):

    if not ('insertTime' in globals()):
        global insertTime
        insertTime = datetime.utcnow()
        # store this time in a file so that it can be retrieved by SkyReporterTest.perfoutput.py
        try:
            timeFileName = perfTestDir + "/dbTime.txt"
            timeFile = open(timeFileName, 'w')
            timeFile.write(str(insertTime))
        except:
            print "!!! cannot open file " + timeFileName + " for writing. Please make sure this is run where you have write permissions\n"
            os.exit(1)


    # many of the types are formatted with a typename, followed by a size in parentheses
    ##### Looking at this more closely, I suppose I could be compiling this once instead of over and over - a bit bottleneck here?
    p = re.compile("(.*)\(([0-9]+).*")


    size = 0
    if (p.match(type)):
        size = int(p.sub(r"\2", type))
        type = p.sub(r"\1", type)
    else:
        size = 0


    if (type == "tinyint"):
        return str(random.randint(1, math.pow(2,7)))
    elif (type == "smallint"):
        return str(random.randint(1, math.pow(2,15)))
    elif (type == "mediumint"):
        return str(random.randint(1, math.pow(2,23)))
    elif (type == "int" or type == "integer"):
        return str(random.randint(1, math.pow(2,31)))
    elif (type == "bigint"):
        return str(random.randint(1, math.pow(2,63)))
    elif (type == "float" or type == "double" or type == "doubleprecision" or type == "decimal" or type == "realdecimal" or type == "numeric"):
        return str(random.random() * 100000000) # random endpoints for this random
    elif (type == "date"):
        insertTime = insertTime - timedelta(seconds=1)
        return "'" + insertTime.strftime("%Y-%m-%d") + "'" 
    elif (type == "datetime"):
        insertTime = insertTime - timedelta(seconds=1)
        return "'" + insertTime.strftime("%Y-%m-%d %H:%M:%S") + "'" 
    elif (type == "timestamp"):
        insertTime = insertTime - timedelta(seconds=1)
        return "'" + insertTime.strftime("%Y%m%d%H%M%S") + "'" 
    elif (type == "time"):
        insertTime = insertTime - timedelta(seconds=1)
        return "'" + insertTime.strftime("%H:%M:%S") + "'" 
    elif (type == "year"):
        insertTime = insertTime - timedelta(seconds=1)
        return "'" + insertTime.strftime("%Y") + "'" 
    elif (type == "char" or type == "varchar" or type == "tinyblog" or type == "tinytext" or type == "blob" or type == "text" or type == "mediumblob"
    or type == "mediumtext" or type == "longblob" or type == "longtext"):
        if (size == 0): # not specified
            return "'a'"
        else:
            lst = [random.choice(string.ascii_letters + string.digits) for n in xrange(size)]
            strn = "".join(lst)
            return strn
    elif (type == "enum"):
        return "NULL"   # TBD if needed
    elif (type == "set"):
        return "NULL"   # TBD if needed
    else:
        print "!!! Unrecognized mysql type: " + type + "\n"
        os.exit(1)

【问题讨论】:

文件是ASCII码。 好吧,这在很大程度上取决于您的代码的功能!它有什么作用? 如果程序是 I/O-bound,你可能不会从切换到 C++ 中受益,但一定要注意适当的缓冲。 写入 80GB,即使是在慢速闪存驱动器上,也不会花费 13 个小时,因此很明显,大部分时间都被 I/O 以外的某种活动占用了。如果没有重要的网络组件(即 13 个小时的大部分时间没有花在等待对远程系统的查询上),那么您可能受到 CPU 和/或内存的限制。如果您的 CPU 受限并且有多个 CPU,线程可能会有所帮助。但是,如果您的内存受限,线程可能会使情况变得更糟。有一些代码可以看可能会很好...... 如果您 profile 您的代码可以准确查看时间花费在哪里,这将有所帮助。您很可能会在 Python 的 I/O 之外的令人惊讶的地方发现瓶颈。 【参考方案1】:

Python 的 I/O 并不比其他语言慢多少。解释器的启动速度可能会很慢,但编写这么大的文件会抵消这种影响。

我建议查看multiprocessing 模块,它可以让您通过拥有多个 Python 实例来获得真正的并行性,这将有助于绕过 GIL。但是,这些会附加一些开销,但同样,对于 80GB 的文件,它应该没有太大关系。请记住,每个进程都是一个完整的进程,这意味着它将占用更多的计算资源。

【讨论】:

【参考方案2】:

另外请记住,根据您的配置,由于您的代码已经是 IO/Bound,您可能会获得低/无加速。如果您从多个线程向它写入单个磁盘,则弊大于利。

【讨论】:

以上是关于Python 多线程性能 - 改用 C++?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C++ 中的多线程会降低性能

将多线程合并到 C++ 中如何提高性能,为啥?

Python的GIL是什么鬼,多线程性能究竟如何

多线程递归程序c++

python-协程多线程多进程性能比较

C++多线程中调用python api函数