source

시간을 병렬화할 수 있는 방법이 있습니까?바둑에서 효과적인 실행 시간을 지키면서 잠은 자나요?

manysource 2023. 8. 18. 22:46

시간을 병렬화할 수 있는 방법이 있습니까?바둑에서 효과적인 실행 시간을 지키면서 잠은 자나요?

저는 Golang에서 느린 쿼리 로그 재생기와 관련된 느린 쿼리 로그 파서 패키지를 개발하고 있습니다.재생기의 경우 다음 코드를 사용합니다(가독성을 위해 주석을 추가했습니다).

for {
        // method from my package that returns a Query object, containing headers values
        // and the query itself
        q := p.GetNext()
        if q == (query.Query{}) {
            break
        }
        db.logger.Tracef("query: %s", q.Query)

        // we send the SQL query to a chan that is read by workers.
        // workers just execute the query on the database, that's all.
        // results from the db query are handled in another piece of the code, it doesn't really
        // matter here
        queries <- q.Query

        // We need a reference time
        if firstPass {
            firstPass = false
            previousDate = q.Time
            continue
        }
        
        // Time field contains the Time: field value in the query header
        now := q.Time
        sleeping := now.Sub(previousDate)
        db.logger.Tracef("next sleeping time: %s", sleeping)
        time.Sleep(sleeping) // Here is my issue. For MariaDB the value is < 0 so no sleep is done

        // For MariaDB, when there is multiple queries in a short amount of
        // time, the Time field is not repeated, so we do not have to update
        // the previous date.
        if now != (time.Time{}) {
            previousDate = now
        }
    }

저는 흥미로운 문제에 부딪혔습니다.MariaDB 느린 쿼리 로그의 경우 두 개 이상의 쿼리가 서로 근접하면 헤더에 시간: 필드가 없으므로 다음과 같은 필드 수가 줄어듭니다.time.Sleep(sleeping)이전 코드 조각에 있습니다.그러나 MySQL 스타일의 느린 쿼리 로그에서는 쿼리 헤더에 항상 시간: 필드가 있습니다. 이는 각 쿼리에 대해 절전 모드가 수행됨을 의미합니다(의 절전 모드 기간에도 마찬가지임).

MariaDB와 MySQL 로그 간의 재생 시간 차이가 매우 크다는 것을 알게 되었습니다. MariaDB 재생 시간은 실시간(로그 파일의 첫 번째 쿼리와 마지막 쿼리 간의 시간 차이)과 상당히 비슷했지만 MySQL 재생 시간은 IRL보다 상당히 높았습니다.가지고 놀고 난 후에pprof나는 문제가 발생했다는 것을 알아차렸습니다.time.Sleep특히 로부터runtime.FutexCPU 시간이 많이 소요됩니다.

벤치마킹을 좀 해봤는데, 기간 결과가 고객의 수와 관련이 있습니다.time.Sleep완료되었습니다(MariaDB보다 MySQL에서 더 높음).

그래서 모든 것을 하는 대신에.time.Sleep단일 스레드에서 유효 시간을 변경하지 않고 병렬로 실행할 수 있는 다른 방법을 찾고 있지만, 이 방법을 찾을 수 없습니다.

제가 제시한 해결책은 다음과 같습니다.

type job struct {
    query string
    idle  time.Time
}

...
    var reference time.Time
    start := time.Now()
    for {
        q := p.GetNext()
        if q == (query.Query{}) {
            s.Stop()
            break
        }
        db.logger.Tracef("query: %s", q.Query)

        r.queries++
        s.Suffix = " queries replayed: " + strconv.Itoa(r.queries)

        // We need a reference time
        if firstPass {
            firstPass = false
            reference = q.Time
        }

        var j job
        delta := q.Time.Sub(reference)
        j.idle = start.Add(delta)
        j.query = q.Query
        db.logger.Tracef("next sleeping time: %s", j.idle)
        jobs <- j
    }

...

func (db database) worker(jobs chan job, errors chan error, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        j, ok := <-jobs
        if !ok {
            db.logger.Trace("channel closed, worker exiting")
            return
        }
        sleep := time.Until(j.idle)
        if sleep > 0 {
            time.Sleep(sleep)
        }
        rows, err := db.drv.Query(j.query)
        if err != nil {
            errors <- err
            db.logger.Debugf("failed to execute query:\n%s\nerror: %s", j.query, err)
        }
        if rows != nil {
            rows.Close()
        }
    }
}

설명:

프로그램 시작을 변수에 저장합니다(여기).start. 다음으로 기준 시간을 설정합니다.reference느린 쿼리 로그 파일의 첫 번째 타임스탬프입니다.절대 변하지 않을 것입니다.

그런 다음 각 새로운 쿼리에서 다음 기간을 계산합니다.reference및 현재 쿼리 타임스탬프q.Time저장해 두자delta.

추가합니다delta로.start타임라인에 타임스탬프가 있습니다(느린 쿼리 로그 파일에서처럼 과거에는 없음).우리는 이 타임스탬프를 우리가 만든 새로운 구조의 쿼리 옆에 있는 작업자에게 보냅니다.job.

작업자가 채널을 통해 작업을 수신하면 쿼리를 수행할 수 있을 때까지 기다리는 시간을 계산합니다.<= 0이면 쿼리를 즉시 실행하고, 그렇지 않으면 대기합니다.

언급URL : https://stackoverflow.com/questions/66982240/is-there-a-way-to-parallelise-time-sleep-but-keeping-the-effective-execution-tim