Skip to content
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

Merged
merged 12 commits into from
Sep 4, 2018

Conversation

crazycs520
Copy link
Contributor

@crazycs520 crazycs520 commented Aug 29, 2018

What problem does this PR solve?

Currently in hash join, TiDB will read inner table first, then build hash table.
This PR will parallel read inner table and build hash table.

What is changed and how it works?

Check List

@crazycs520 crazycs520 added type/enhancement The issue or PR belongs to an enhancement. status/WIP labels Aug 29, 2018
@zz-jason
Copy link
Member

@XuHuaiyu PTAL

@crazycs520
Copy link
Contributor Author

/run-all-tests

Copy link
Contributor

@XuHuaiyu XuHuaiyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done, rest LGTM

executor/join.go Outdated
chk := e.innerResult.GetChunk(i)
for j := 0; j < chk.NumRows(); j++ {

numChks := 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/ numChks/ chkIdx := uint32(0)

executor/join.go Outdated
numChks := 0
for chk := range chkCh {
numRows := chk.NumRows()
for j := 0; j < numRows; j++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

j < chk.NumRows()

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

BenchmarkFor1    5000000              1115 ns/op
BenchmarkFor1    5000000              1119 ns/op
BenchmarkFor1    5000000              1106 ns/op
BenchmarkFor2   10000000               557 ns/op
BenchmarkFor2   10000000               577 ns/op
BenchmarkFor2   10000000               587 ns/op

so, I think the original is better.

executor/join.go Outdated
@@ -495,6 +515,8 @@ func (e *HashJoinExec) Next(ctx context.Context, chk *chunk.Chunk) (err error) {
}

func (e *HashJoinExec) fetchInnerAndBuildHashTable(ctx context.Context) {
chkCh := make(chan *chunk.Chunk, e.concurrency)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. move this two definition to after the defer func.
  2. add a comment for chkCh. (s/ chkCh/ innerResultCh ?)

executor/join.go Outdated
e.innerFinished <- errors.Trace(err)
return
close(doneCh)
// Func fetchInnerRows may blocked by this channel, so read from the channel to unblock it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. remove Func
  2. s/ blocked/ be blocked

Copy link
Contributor

@XuHuaiyu XuHuaiyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zz-jason
Copy link
Member

@crazycs520 Could you post some performance result?

@crazycs520
Copy link
Contributor Author

@zz-jason

  • TiDB master on tpch-19.sql
    query 19 takes time 33.139627742767334s
  • this branch
    query 19 takes time 31.621350860595703s

// 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)
Copy link
Contributor

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?

Copy link
Member

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.

Copy link
Contributor Author

@crazycs520 crazycs520 Sep 4, 2018

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 and tid1 both have 10 rows. and the join result is 1 row;
master:

goos: linux
goarch: amd64
pkg: sql_script
BenchmarkSelect 	    5000	   3474578 ns/op
PASS
ok  	sql_script	17.852s

this branch

goos: linux
goarch: amd64
pkg: sql_script
BenchmarkSelect 	    5000	   3486767 ns/op
PASS
ok  	sql_script	17.904s


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this TODO can be removed? @XuHuaiyu how do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's both ok to keep it or remove it.

@zz-jason
Copy link
Member

zz-jason commented Sep 4, 2018

LGTM

@zz-jason
Copy link
Member

zz-jason commented Sep 4, 2018

@XuHuaiyu PTAL

@zz-jason zz-jason added sig/execution SIG execution status/LGT1 Indicates that a PR has LGTM 1. labels Sep 4, 2018
@zz-jason
Copy link
Member

zz-jason commented Sep 4, 2018

/run-all-tests

Copy link
Contributor

@XuHuaiyu XuHuaiyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's both ok to keep it or remove it.

@zz-jason zz-jason added status/LGT2 Indicates that a PR has LGTM 2. and removed status/LGT1 Indicates that a PR has LGTM 1. labels Sep 4, 2018
@zz-jason zz-jason added this to the 2.1 milestone Sep 4, 2018
@zz-jason zz-jason merged commit ac8a61e into pingcap:master Sep 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sig/execution SIG execution status/LGT2 Indicates that a PR has LGTM 2. type/enhancement The issue or PR belongs to an enhancement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants