2014-06-06 3 views
2

그래서 기본적으로 80GB 이상의 파일을 작성하는 Python 스크립트가 있습니다. 현재는 순차적으로 실행되며 실제로 서버를 실행할 때만 약 13 시간이 걸립니다.파이썬 멀티 스레딩 성능 - 대신 C++ 사용?

필자는 병렬화하여 하나가 아닌 많은 파일을 씁니다.

내가 이미 가지고있는 것을 파이썬에서 유지하는 것이 약간 쉬울 것이지만 다중 스레드를 통합 할 것입니다 (액세스 할 필요가있는 공유 데이터의 단일 맵이 있습니다. 그래서 아무도 쓰지 않을 것입니다. t 보호가 필요함).

그러나 파이썬에서 유지하는 것은 어리석은가요? 나는 C++도 알고 있으므로 C++로 다시 작성해야한다고 생각합니까? 나는이 프로그램이 다른 어떤 것보다 더 많은 디스크에 묶여 있다는 것을 알 수있다. (파일을 쓰는 데 사용 된 톤이 없다.) 아마 차이가별로 없을 것이다. 나는 C + +가 똑같은 80GB 파일을 (연속적으로) 쓰는 데 얼마나 오래 걸릴지는 잘 모르겠다.


UPDATE 6/6/14, 태평양 표준시 16시 40분 : 나는 그것이 순전히 디스크 바운드되는 반대로 코드 자체에 병목 현상이 있는지 확인하기 위해 아래에있는 내 코드를 게시하고있다.

약 30 개의 테이블이있는 테이블 당 한 번 writeEntriesToSql()을 호출합니다. "size"는 테이블에 기록 할 삽입 수입니다. 모든 테이블의 누적 크기는 약 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) 
+0

파일은 ASCII 형식입니다. – vmayer

+2

글쎄, 이것은 당신의 코드가하는 것에 상당히 의존한다! 그것은 무엇을합니까? – Cameron

+1

프로그램이 I/O 바인딩 인 경우 C++로 전환 할 때 많은 이점을 얻지 못할 것이지만 적절한 버퍼링을 처리해야합니다. – Tibor

답변

3

파이썬의 I/O는 다른 언어보다 훨씬 느립니다. 통역사는 시작하는 속도가 느릴 수 있지만 큰 파일을 작성하면 그 효과가 상각됩니다.

multiprocessing 모듈을 살펴 보는 것이 좋습니다.이 모듈을 사용하면 GIL 주위를 돌아 다니는 데 도움이되는 여러 개의 Python 인스턴스를 사용하여 진정한 병렬 처리를 수행 할 수 있습니다. 그러나 이러한 파일에는 약간의 오버 헤드가 있지만 80GB 파일로는 너무 중요하지 않습니다. 각 프로세스는 완전한 프로세스이므로 더 많은 계산 리소스가 필요하다는 것을 명심하십시오.

1

또한 구성에 따라 코드가 이미 IO/바운딩 (I/Bound)되어 있기 때문에 속도가 느려지거나 빨라질 수도 있습니다. 많은 스레드에서 단일 디스크 쓰기를 사용하는 것이 좋을 때보 다 해를 끼칠 수 있습니다.