-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
executor: parallel read inner table and build hash table. #7544
Changes from 10 commits
8b3fbfb
3f6e4fe
4b5314e
b6eca1b
aa97229
d7050e9
e6dcfe8
9b68659
b757eb8
1f5189c
f35b4e5
aad63a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -263,20 +263,40 @@ func (e *HashJoinExec) wait4Inner() (finished bool, err error) { | |
|
||
// fetchInnerRows fetches all rows from inner executor, | ||
// and append them to e.innerResult. | ||
func (e *HashJoinExec) fetchInnerRows(ctx context.Context) (err error) { | ||
func (e *HashJoinExec) fetchInnerRows(ctx context.Context, chkCh chan<- *chunk.Chunk, doneCh chan struct{}) { | ||
defer func() { | ||
close(chkCh) | ||
if r := recover(); r != nil { | ||
buf := make([]byte, 4096) | ||
stackSize := runtime.Stack(buf, false) | ||
buf = buf[:stackSize] | ||
log.Errorf("hash join inner fetcher panic stack is:\n%s", buf) | ||
} | ||
}() | ||
e.innerResult = chunk.NewList(e.innerExec.retTypes(), e.maxChunkSize) | ||
e.innerResult.GetMemTracker().AttachTo(e.memTracker) | ||
e.innerResult.GetMemTracker().SetLabel("innerResult") | ||
var err error | ||
for { | ||
if e.finished.Load().(bool) { | ||
return nil | ||
} | ||
chk := e.children[e.innerIdx].newChunk() | ||
err = e.innerExec.Next(ctx, chk) | ||
if err != nil || chk.NumRows() == 0 { | ||
return errors.Trace(err) | ||
select { | ||
case <-doneCh: | ||
return | ||
default: | ||
if e.finished.Load().(bool) { | ||
return | ||
} | ||
chk := e.children[e.innerIdx].newChunk() | ||
err = e.innerExec.Next(ctx, chk) | ||
if err != nil { | ||
e.innerFinished <- errors.Trace(err) | ||
return | ||
} | ||
if chk.NumRows() == 0 { | ||
return | ||
} | ||
chkCh <- chk | ||
e.innerResult.Add(chk) | ||
} | ||
e.innerResult.Add(chk) | ||
} | ||
} | ||
|
||
|
@@ -512,26 +532,31 @@ func (e *HashJoinExec) fetchInnerAndBuildHashTable(ctx context.Context) { | |
} | ||
close(e.innerFinished) | ||
}() | ||
|
||
if err := e.fetchInnerRows(ctx); err != nil { | ||
e.innerFinished <- errors.Trace(err) | ||
return | ||
} | ||
// innerResultCh transfer inner result chunk from inner fetch to build hash table. | ||
innerResultCh := make(chan *chunk.Chunk, e.concurrency) | ||
doneCh := make(chan struct{}) | ||
go e.fetchInnerRows(ctx, innerResultCh, doneCh) | ||
|
||
if e.finished.Load().(bool) { | ||
return | ||
} | ||
|
||
if err := e.buildHashTableForList(); err != nil { | ||
// TODO: Parallel build hash table. Currently not support because `mvmap` is not thread-safe. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this TODO can be removed? @XuHuaiyu how do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's both ok to keep it or remove it. |
||
err := e.buildHashTableForList(innerResultCh) | ||
if err != nil { | ||
e.innerFinished <- errors.Trace(err) | ||
return | ||
close(doneCh) | ||
// fetchInnerRows may be blocked by this channel, so read from the channel to unblock it. | ||
select { | ||
case <-innerResultCh: | ||
default: | ||
} | ||
} | ||
} | ||
|
||
// buildHashTableForList builds hash table from `list`. | ||
// key of hash table: hash value of key columns | ||
// value of hash table: RowPtr of the corresponded row | ||
func (e *HashJoinExec) buildHashTableForList() error { | ||
func (e *HashJoinExec) buildHashTableForList(innerResultCh chan *chunk.Chunk) error { | ||
e.hashTable = mvmap.NewMVMap() | ||
e.innerKeyColIdx = make([]int, len(e.innerKeys)) | ||
for i := range e.innerKeys { | ||
|
@@ -543,23 +568,26 @@ func (e *HashJoinExec) buildHashTableForList() error { | |
keyBuf = make([]byte, 0, 64) | ||
valBuf = make([]byte, 8) | ||
) | ||
for i := 0; i < e.innerResult.NumChunks(); i++ { | ||
|
||
chkIdx := uint32(0) | ||
for chk := range innerResultCh { | ||
if e.finished.Load().(bool) { | ||
return nil | ||
} | ||
chk := e.innerResult.GetChunk(i) | ||
for j := 0; j < chk.NumRows(); j++ { | ||
numRows := chk.NumRows() | ||
for j := 0; j < numRows; j++ { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. j < chk.NumRows() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. func BenchmarkFor1(b *testing.B) {
chk := getChks()
for i := 0; i < b.N; i++ {
for j := 0; j < chk.NumRows(); j++ {
_ = j
}
}
}
func BenchmarkFor2(b *testing.B) {
chk := getChks()
for i := 0; i < b.N; i++ {
numRows := chk.NumRows()
for j := 0; j < numRows; j++ {
_ = j
}
}
} chk.NumRows is 1024.
so, I think the original is better. |
||
hasNull, keyBuf, err = e.getJoinKeyFromChkRow(false, chk.GetRow(j), keyBuf) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
if hasNull { | ||
continue | ||
} | ||
rowPtr := chunk.RowPtr{ChkIdx: uint32(i), RowIdx: uint32(j)} | ||
rowPtr := chunk.RowPtr{ChkIdx: chkIdx, RowIdx: uint32(j)} | ||
*(*chunk.RowPtr)(unsafe.Pointer(&valBuf[0])) = rowPtr | ||
e.hashTable.Put(keyBuf, valBuf) | ||
} | ||
chkIdx++ | ||
} | ||
return nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about don't always fork new groutine in here and keep fetchInnerRows logic in "primary" goroutine, and base on fetch result to choose whether or not to fork new groutine to buildHashTableForList.(e.g. Next 2 times but still has data?)
IMHO, The idea is that maybe common small innser case it's too heavy to fork goroutine but some case fork is worth, and fetch with single goutine is required, so make choosen in buildTable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@crazycs520 We can bench the scenario that the inner child for the hash join operator is a simple table reader and the number of output records of that child is very small, for example, 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lysu @zz-jason here is a benchmark:
select t1.* from t1 inner join tid1 where t1.id=tid1.id and tid1.id < 1;
table
t1
andtid1
both have 10 rows. and the join result is 1 row;master:
this branch