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/join : use shallow copy for join. #7433

Merged
merged 49 commits into from
Aug 29, 2018

Conversation

crazycs520
Copy link
Contributor

@crazycs520 crazycs520 commented Aug 19, 2018

What problem does this PR solve?

This is for #7420

In joiner.go, function tryToMatch is act like below:

j.chk.Reset()			
if j.outerIsRight {	
    j.makeJoinRowToChunk(j.chk, inner, outer)	
} else {	
    j.makeJoinRowToChunk(j.chk, outer, inner)	
}	
matched, err = expression.EvalBool(j.ctx, j.conditions, j.chk.GetRow(0))
...

makeJoinRowToChunk will copy inner and outer row to temporary chunk( j.chk ).

There's no need to do deep copy, use shadow copy will be ok, Specifically, chunk.column.data use shadow copy will be ok.

Another thing worth to say is that function call overhead in makeJoinRowToChunk is very expensive,Maybe more expensive than the actual memory cory. such as column.isNull , column.appendNullBitmap. see here pprof svg:fieldbyfield.pdf

This PR will use shadow copy for chunk.column.data, and remove column.appendNullBitmap funcation call.

Below is a benchmarck:

~/code/goread/src/github.com/pingcap/tidb/util/chunk (column-copy ✔) ᐅ go test -bench="BenchmarkCopy*" -benchtime="3s" -count=3 -run=xx
goos: darwin
goarch: amd64
pkg: github.com/pingcap/tidb/util/chunk
BenchmarkCopyFieldByField-8        50000            101054 ns/op               4 B/op          0 allocs/op
BenchmarkCopyFieldByField-8        50000            102616 ns/op               4 B/op          0 allocs/op
BenchmarkCopyFieldByField-8        50000             99987 ns/op               4 B/op          0 allocs/op
BenchmarkCopyShadow-8             100000             48771 ns/op               0 B/op          0 allocs/op
BenchmarkCopyShadow-8             100000             47126 ns/op               0 B/op          0 allocs/op
BenchmarkCopyShadow-8             100000             49391 ns/op               0 B/op          0 allocs/op
PASS
ok      github.com/pingcap/tidb/util/chunk      34.265s
create table t1 (id int, name varchar(30),addr varchar(30), course varchar(30))
create table t2 (id int, name varchar(30),addr varchar(30), course varchar(30))

# both t1, t2 data as below:
+------+----------------+-------------------+------------------+
| id   | name           | addr              | course           |
+------+----------------+-------------------+------------------+
| 0    | name_abcd_0    | address_abcd_0    | course_abcd_0    |
| 1    | name_abcd_1    | address_abcd_1    | course_abcd_1    |
| 2    | name_abcd_2    | address_abcd_2    | course_abcd_2    |
| 3    | name_abcd_3    | address_abcd_3    | course_abcd_3    |
...
10000 rows

# new
mysql root@127.0.0.1:test> select count(*) from t1,t2 where t1.id != t2.id and t1.name != t2.name and t1.addr != t2.addr and t1.course != t2.course and t1.addr > t2.course;
+----------+
| count(*) |
+----------+
| 0        |
+----------+
1 row in set
Time: 7.94s
# remove t1.addr > t2.course condition always false.
mysql root@127.0.0.1:test> select count(*) from t1,t2 where t1.id != t2.id and t1.name != t2.name and t1.addr != t2.addr and t1.course != t2.course; 
+----------+
| count(*) |
+----------+
| 99990000 |
+----------+
1 row in set
Time: 16.76s

#---------------------------------------------------------------------------------
# old
mysql root@127.0.0.1:test> select count(*) from t1,t2 where t1.id != t2.id and t1.name != t2.name and t1.addr != t2.addr and t1.course != t2.course and t1.addr > t2.course;
+----------+
| count(*) |
+----------+
| 0        |
+----------+
1 row in set
Time: 16.13s
# remove t1.addr > t2.course condition always false.
mysql root@127.0.0.1:test> select count(*) from t1,t2 where t1.id != t2.id and t1.name != t2.name and t1.addr != t2.addr and t1.course != t2.course; 
+----------+
| count(*) |
+----------+
| 99990000 |
+----------+
1 row in set
Time: 18.46s
count(*) = 0 count(*) = 99990000
Time of new 7.94s 16.76s
Time of old 16.13s 18.46s

As you can see, if have t1.addr > t2.coursecondition always false, the result of count(*) = 0, temporary deep copy in old is spend more time.

After remove t1.addr > t2.coursecondition , the result of new is still have a little advantage than old.

create table t1 (id int);
create table t2 (id int);
# both t1, t2 data as below:
+-------+
| id    |
+-------+
| 0     |
| 1     |
| 2     |
| ...   |
| ...   |
| ...   |
| 10000 |
+-------+

# new
mysql root@127.0.0.1:test> select count(*) from t1 inner join t2 where t1.id != t2.id;
+----------+
| count(*) |
+----------+
| 99990000 |
+----------+
1 row in set
Time: 8.18s
#----------------------------------------------------------------------------------
# old
mysql root@127.0.0.1:test> select count(*) from t1 inner join t2 where t1.id != t2.id;
+----------+
| count(*) |
+----------+
| 99990000 |
+----------+
1 row in set
Time: 7.74s
count(*) = 99990000
Time of new 8.18s
Time of old 7.74s

If both join column is int , shadow copy will have no advantage, and maybe slow than old version.

@winkyao
Copy link
Contributor

winkyao commented Aug 19, 2018

@crazycs520 Please rebase your commit first, then push it to github.

@crazycs520
Copy link
Contributor Author

crazycs520 commented Aug 19, 2018

below are pprof svg of columnbycolumn and fieldbyfield.
columnbycolumn.pdf
fieldbyfield.pdf

@zz-jason
Copy link
Member

Seems like the improvement from the cache locality is counteracted by the overhead of some extra cpu instructions.

@crazycs520
Copy link
Contributor Author

crazycs520 commented Aug 19, 2018

At present, Most of time is consume by the Iterator interface and another function call.
Maybe get the chunk from Iterator then copy between chunk is better.

Another idea is don't copy, store indexSlice of column in chunk.
When do join, Combine the two columns in inner and outer directly and rewrite indexSlice to control chunk.GetRow..... This idea needs to be considered carefully.

@@ -160,6 +160,77 @@ func (c *Chunk) AppendPartialRow(colIdx int, row Row) {
}
}

func (c *Chunk) AppendPartialRows(colIdx int, rowIt Iterator, maxLen int) int {
Copy link
Contributor

Choose a reason for hiding this comment

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

For the exported method, you must comment. It is not optional.

return c.columns[colIdx+0].length - oldRowLen
}

func (c *Chunk) AppendPartialSameRows(colIdx int, row Row, rowsLen int) {
Copy link
Contributor

Choose a reason for hiding this comment

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

same reason.

@@ -32,6 +32,8 @@ type Iterator interface {
// Next returns the next Row.
Next() Row

PreRows(i int)
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the use case for this method?

@@ -75,6 +77,11 @@ func (it *iterator4Slice) Next() Row {
return row
}

// PreRows implements the Iterator interface.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment basically says nothing. You have to be more explicitly.

@iamxy
Copy link
Member

iamxy commented Aug 20, 2018

/rebuild

@iamxy
Copy link
Member

iamxy commented Aug 20, 2018

/run-all-tests

1 similar comment
@lysu
Copy link
Contributor

lysu commented Aug 20, 2018

/run-all-tests

@crazycs520 crazycs520 reopened this Aug 22, 2018
@crazycs520 crazycs520 force-pushed the column-copy branch 2 times, most recently from ab33e54 to 71c54a5 Compare August 22, 2018 06:49
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.

change all the shadow to shallow

@@ -125,6 +126,7 @@ type baseJoiner struct {
defaultInner chunk.Row
outerIsRight bool
chk *chunk.Chunk
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. Inner join, left out join and right out join will use chk to do deep copy. deep copy + vectorize filter + batch copy have better performance.

@@ -142,6 +144,16 @@ func (j *baseJoiner) makeJoinRowToChunk(chk *chunk.Chunk, lhs, rhs chunk.Row) {
chk.AppendPartialRow(lhs.Len(), rhs)
}

// makeJoinRow combines inner, outer row into shadowRow.
// combines will uses shadow copy inner and outer row data to shadowRow.
Copy link
Contributor

Choose a reason for hiding this comment

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

makeJoinRow shallow copies `inner` and `outer` into `shallowRow`.

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.

change all the shadow to shallow
rest LGTM

@crazycs520 crazycs520 changed the title executor/join : use shadow copy for join. executor/join : use shallow copy for join. Aug 27, 2018
@crazycs520
Copy link
Contributor Author

crazycs520 commented Aug 27, 2018

@zz-jason PTAL

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

for i, rowCol := range row.c.columns {
chkCol := chk.columns[colIdx+i]
if !rowCol.isNull(row.idx) {
chkCol.nullBitmap[0] = 1
Copy link
Member

Choose a reason for hiding this comment

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

It's better to add some comments about why we set the whole byte to 1/0

// ShallowCopyPartialRow shallow copies the data of `row` to MutRow.
func (mr MutRow) ShallowCopyPartialRow(colIdx int, row Row) {
chk := mr.c
for i, rowCol := range row.c.columns {
Copy link
Member

Choose a reason for hiding this comment

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

how about:
s/rowCol/srcCol/
s/chkCol/dstCol/


// ShallowCopyPartialRow shallow copies the data of `row` to MutRow.
func (mr MutRow) ShallowCopyPartialRow(colIdx int, row Row) {
chk := mr.c
Copy link
Member

Choose a reason for hiding this comment

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

chk is only used once, I think we can remove this variable and use mr.c directly.

@@ -142,6 +149,15 @@ func (j *baseJoiner) makeJoinRowToChunk(chk *chunk.Chunk, lhs, rhs chunk.Row) {
chk.AppendPartialRow(lhs.Len(), rhs)
}

// makeJoinRow shallow copies `inner` and `outer` into `shallowRow`.
func (j *baseJoiner) makeJoinRow(isRightJoin bool, inner, outer chunk.Row) {
Copy link
Member

Choose a reason for hiding this comment

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

how about s/makeJoinRow/makeShallowJoinRow/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@@ -102,18 +101,25 @@ func newJoiner(ctx sessionctx.Context, joinType plan.JoinType,
}
switch joinType {
case plan.SemiJoin:
base.shallowRow = chunk.MutRowFromTypes(colTypes)
Copy link
Member

Choose a reason for hiding this comment

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

What not change it on line 94 directly? So we don't repeat it multiple times?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

only SemiJoin, AntiSemiJoin, LeftOuterSemiJoin, AntiLeftOuterSemiJoin need shallowRow.

@crazycs520
Copy link
Contributor Author

/run-all-tests

Copy link
Member

@zz-jason zz-jason 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 zz-jason added status/LGT1 Indicates that a PR has LGTM 1. status/all tests passed labels Aug 28, 2018
@zz-jason
Copy link
Member

@XuHuaiyu @winoros PTAL

winkyao
winkyao previously approved these changes Aug 28, 2018
Copy link
Contributor

@winkyao winkyao 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 zz-jason added status/LGT2 Indicates that a PR has LGTM 2. and removed status/LGT1 Indicates that a PR has LGTM 1. labels Aug 29, 2018
@crazycs520 crazycs520 merged commit 360567b into pingcap:master Aug 29, 2018
@crazycs520 crazycs520 deleted the column-copy branch August 29, 2018 07:58
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.

9 participants