Run Vectorized Queries

When handling large datasets, the vectorization execution engine can greatly improve computing efficiency. By vectorizing data, multiple data elements can be processed at the same time, using parallel computing and SIMD instruction sets to speed up the process. SynxDB Vectorization (referred to as Vectorization) is a vectorization plugin based on the SynxDB kernel, designed to optimize query performance.

Enable vectorization

  1. Before enabling SynxDB Vectorization, you need to install and deploy SynxDB.

  2. Connect to the target database. Replace your_database_name with the actual name of your database.

    $ psql your_database_name;
    
  3. Enable vectorization in the database.

    SET vector.enable_vectorization = ON;
    

Usage

Vectorization provides the following parameters for users:

Parameter name

Description

vector.enable_vectorization

Controls whether vectorized queries are enabled. It is disabled by default.

vector.max_batch_size

The size of the vectorized batch, which controls how many rows the executor processes in one cycle. The range is [0, 163840], and the default value is 16384.

vector.hashjoin_spill_memory_mb

Memory budget in MB for the hash join build side. When the estimated hash table size exceeds this value, data is spilled to disk and processed in partitions. The range is [0, 2147483647], and the default value is 512. Setting to 0 disables spill (no spill).

vector.topk_bound_threshold

Maximum K value (LIMIT + OFFSET) for which the vectorized executor replaces a full sort with a bounded-heap TopK operator. The range is [0, 10000000] and the default value is 2000.

vector.topk_runtime_filter

Whether to skip PAX row groups whose column min/max falls outside the current TopK threshold for ORDER BY ... LIMIT N queries. The default value is on.

vector.limit_hashagg_max_total

Upper bound on OFFSET + LIMIT at which the executor applies the Limit+HashAgg early-stop optimization. The range is [0, 10000], and the default value is 1000. Setting to 0 disables the optimization.

vector.sonic_motion_direct_send

Controls whether a vectorized hash aggregate routes its output batches directly to the redistribute Motion, skipping per-row rehashing. Disabled by default (off).

vector.winagg_spill_memory_mb

Memory budget in MB for WindowHashAgg spill-to-disk. When the estimated working memory exceeds this value, data is spilled to disk and processed in partitions. The range is [0, 2147483647], and the default value is 512. Setting to 0 disables spill (no spill).

Enable or disable vectorization queries

You can temporarily enable or disable the vectorization feature in a connection by setting the vector.enable_vectorization variable. This setting is effective only for the current connection, and it will reset to the default value after disconnecting.

After uninstalling vectorization, setting the vector.enable_vectorization variable will have no effect. When you reinstall it, the vector.enable_vectorization will be restored to the default value off.

SET vector.enable_vectorization TO [on|off];

Set vectorization batch size

The batch size of vectorization greatly affects performance. If the value is too small, queries might slow down. If the value is too large, it might increase memory usage without improving performance.

SET vector.max_batch_size TO <batch_number>;

Set hash join spill memory budget

Note

This parameter only takes effect when vectorization is enabled.

When a vectorized hash join processes a large build side that exceeds available memory, it can cause out-of-memory failures. The vector.hashjoin_spill_memory_mb parameter controls the memory budget for the hash join build side. When the estimated hash table size exceeds this budget, data is automatically spilled to disk and processed in partitions, preventing out-of-memory failures on large hash joins.

The default value is 512 MB, which is suitable for most workloads. You can increase this value if your system has sufficient memory and you want to reduce disk spills, or decrease it to conserve memory in resource-constrained environments. Setting it to 0 disables spill-to-disk entirely.

-- Set the hash join spill memory budget to 1024 MB.
SET vector.hashjoin_spill_memory_mb = 1024;

-- Disable spill-to-disk (not recommended for large hash joins).
SET vector.hashjoin_spill_memory_mb = 0;

Speed up ORDER BY … LIMIT N with TopK

Note

This parameter only takes effect when vectorization is enabled.

For ORDER BY ... LIMIT N queries, the vectorized executor can replace a full sort with a TopK operator that maintains a bounded heap of the best N rows. Only rows that beat the heap’s current worst value are kept, so the executor never materializes a full sorted set.

The vector.topk_bound_threshold parameter controls when the executor applies this replacement. When LIMIT + OFFSET is less than or equal to the threshold (default 2000), the executor builds a TopK node. For larger values, or when the plan shape is Limit → Result → Sort, the executor falls back to a full sort followed by row selection.

A non-zero OFFSET produces the Limit → Result → Sort plan shape, so a query that uses OFFSET falls back to a full sort even when LIMIT + OFFSET stays within the threshold. In practice, TopK applies to LIMIT-only queries.

To raise or lower the boundary, set the parameter for the current session:

-- Allow TopK for LIMIT values up to 5000.
SET vector.topk_bound_threshold = 5000;

-- Disable the TopK plan optimization.
SET vector.topk_bound_threshold = 0;

To confirm that a query runs with TopK, use EXPLAIN and look for a Vec Sort Method line under the Sort node:

gpadmin=# EXPLAIN SELECT * FROM events ORDER BY ts DESC LIMIT 100;
                                    QUERY PLAN
--------------------------------------------------------------------------
 Vec Limit  (cost=...)
    ->  Vec Gather Motion 3:1  (slice1; segments: 3)  (cost=...)
            ->  Vec Sort  (cost=...)
                    Sort Key: ts DESC
                    Vec Sort Method:  TopK  K: 100
                    ->  Vec Seq Scan on events  (cost=...)

If the Vec Sort Method line is absent, the query is using OrderByNode instead, either because LIMIT + OFFSET exceeds the threshold or because the plan shape is ineligible.

Skip PAX row groups for TopK queries

Note

This parameter only takes effect on PAX tables with vectorization enabled.

For ORDER BY ... LIMIT N queries that run under the vectorized executor, the TopK operator keeps a heap of the best N rows seen so far. The vector.topk_runtime_filter parameter lets the executor push the heap’s current worst value down to the PAX scan as a runtime threshold. The scan then compares each row group’s stored column min/max statistics against the threshold and skips any group whose values cannot improve the top N result, avoiding I/O on those groups entirely.

The parameter is on by default. Turn it off when you need to diagnose a result discrepancy or compare execution without group-level skipping:

-- Disable group-level skip for the current session.
SET vector.topk_runtime_filter = off;

The filter applies only when the sort key resolves to a base table column whose min/max statistics PAX tracks. Queries whose sort expression references a computed value, or whose plan shape prevents the executor from mapping the sort column back to a base table column, scan all row groups.

Hash aggregate execution engines

The vectorized executor runs hash aggregation on one of two engines: a default fast-path engine (referred to as Sonic in EXPLAIN output) for most analytic aggregations, and a legacy engine (referred to as Normal) for queries that fall outside the fast-path’s supported set or that trigger a specialized routing rule. Both engines return identical results; only execution speed differs.

The executor routes a query to the Normal engine when any of the following applies:

  • The query uses the Limit+HashAgg early-stop optimization. See Speed up top-N group queries with Limit+HashAgg early stop.

  • The query is a GROUP BY without aggregate functions, or a SELECT DISTINCT.

  • A group-key column is of a type not yet supported by the fast-path engine. The fast-path engine currently supports smallint, integer, bigint, date, time, timestamp, text, bytea, and numeric.

  • An aggregate function is not yet supported by the fast-path engine. Currently supported: count, sum over integer or numeric columns, min over text or bytea columns, and avg over numeric columns.

For a numeric column to participate in vectorized aggregation, whether as a group key or as an aggregate input, the column must be declared with an explicit precision and scale (for example, numeric(20, 4)). A numeric column declared without them is not vectorized, and the whole query runs on the row engine.

In a distributed query, hash aggregation runs in two stages: a partial stage on each segment, and a finalize stage after redistribution. EXPLAIN VERBOSE reports a Vec HashAgg Method: line for each stage, so a single query can show different engines for its two stages. In particular, when the query aggregates with sum over a numeric column, the finalize stage runs on the Normal engine even though the partial stage uses the fast-path engine. sum over integer columns and avg over numeric columns use the fast-path engine in both stages.

To check which engine handles a hash aggregate, run EXPLAIN VERBOSE and look for the Vec HashAgg Method: line under the Vec HashAggregate node:

EXPLAIN VERBOSE SELECT key, count(*) FROM t GROUP BY key;

The value is one of Sonic (fast path), Normal (legacy path), or Limit+HashAgg (legacy path with the early-stop bound applied).

Speed up top-N group queries with Limit+HashAgg early stop

When a GROUP BY query feeds directly into a LIMIT clause without an ORDER BY in between, the vectorized executor can stop aggregating once it has produced the first N groups, instead of building the complete hash table over all input. This early-stop path delivers roughly a 3x speedup on “top-N by group” workloads such as TPC-H Q18.

The vector.limit_hashagg_max_total parameter sets the upper bound on OFFSET + LIMIT for which the optimization is applied. The default value is 1000; the hard ceiling is 10000, beyond which the per-group tracking overhead outweighs the early-stop benefit. Setting the parameter to 0 disables the optimization. The executor applies it automatically when all of the following conditions hold:

  • The aggregation uses the HashAgg strategy.

  • The LIMIT operator is the direct parent of the HashAgg (no ORDER BY or other operator in between).

  • Both LIMIT and any OFFSET are constant integer literals, not parameters or expressions.

  • OFFSET + LIMIT is between 1 and the value of vector.limit_hashagg_max_total.

When any condition is not met, the query falls back to the standard vectorized HashAgg path and returns identical results without the early-stop benefit. To disable the optimization, for example to compare execution time or to diagnose a plan, set the parameter to 0:

-- Disable Limit+HashAgg early stop for the current session.
SET vector.limit_hashagg_max_total = 0;

-- Raise the threshold to allow early stop on larger top-N queries.
SET vector.limit_hashagg_max_total = 5000;

Queries that take this path run on the Normal engine (see Hash aggregate execution engines), because the fast-path engine does not yet honor the early-stop bound.

Reduce cross-segment latency with direct-send routing

For distributed analytic queries that redistribute the result of a hash aggregation across segments, the vectorized executor can attach a segment tag to each output batch inside the hash aggregate, allowing the Motion operator to forward the batch as-is instead of rehashing every row. This direct-send routing reduces the per-row overhead of cross-segment data movement and lowers latency on queries that span segments. The optimization applies only on the Sonic engine path (see Hash aggregate execution engines).

The vector.sonic_motion_direct_send parameter controls this optimization and is disabled by default. To opt in for the current session:

SET vector.sonic_motion_direct_send = on;

The executor applies the optimization automatically when all of the following conditions hold:

  • The query uses a hash redistribute Motion directly above a vectorized hash aggregate.

  • The cluster has more than one segment.

  • Every column in the redistribution key is one of the supported types: smallint, integer, bigint, text, timestamp, time, or date.

  • The redistribution key is a subset of the GROUP BY columns. Redistribution on an aggregate result (for example, SUM(x)) is not currently supported.

When any condition is not met, the query falls back to standard hash redistribution and returns identical results without the latency benefit.

To check whether direct-send applies to a query, look for Direct Send: yes in the EXPLAIN output on the corresponding Motion node:

EXPLAIN SELECT key, count(*) FROM t GROUP BY key;

When the GUC is on and the query plan matches the required structure, the Motion line shows Direct Send: yes. The annotation reflects only the conditions visible at plan time, so the runtime executor might still fall back, for example, when a column type is not supported.

Execution methods for window functions

With vectorization enabled, window functions run in one of two ways: sort-based window aggregation (Vec WindowAgg) or hash-based window aggregation (Vec WindowHashAgg). The optimizer chooses between them as follows:

  • When every window in the query has a PARTITION BY, the optimizer uses sort-based window aggregation by default. If the query also carries a Top-K qualification, that is, <= or < constrains the output of rank(), dense_rank(), or row_number(), the plan includes Vec Partition Top-K, which prunes each partition down to its candidate rows before sorting. vector.enable_partition_topk controls this behavior and defaults to on.

  • When a window has no PARTITION BY, the optimizer uses hash-based window aggregation by default.

vector.enable_window_hash_agg controls whether the optimizer can use hash-based window aggregation. It defaults to on. When you set it to off, all window functions use sort-based window aggregation.

Warning

Under hash-based window aggregation, rank() can return wrong ranks without raising an error. The problem appears when many rows take part in the window computation (about 200,000 rows or more). The problem does not affect row_number(), dense_rank(), percent_rank(), or aggregate window functions.

For queries that use rank(), disable hash-based window aggregation in the session, and keep vector.enable_partition_topk at its default value of on:

SET vector.enable_window_hash_agg = off;

You can check which execution method a window function uses in the EXPLAIN output. The window in the following query has a PARTITION BY and a Top-K qualification, so it uses sort-based window aggregation with Vec Partition Top-K:

EXPLAIN (COSTS OFF)
SELECT * FROM (
    SELECT g, k, rank() OVER (PARTITION BY g ORDER BY v DESC) AS r FROM t
) s WHERE r <= 3;
                       QUERY PLAN
---------------------------------------------------------
 Vec Gather Motion 2:1  (slice1; segments: 2)
   ->  Vec Result
         Filter: ((rank() OVER (?)) <= 3)
         ->  Vec WindowAgg
               Partition By: g
               Order By: v
               ->  Vec Sort
                     Sort Key: g, v DESC
                     ->  Vec Partition Top-K
                           ->  Vec Seq Scan on t
 Optimizer: GPORCA

Set window aggregate spill memory budget

Note

This parameter only takes effect when vectorization and the GPORCA optimizer are enabled, because the WindowHashAgg operator is generated only by GPORCA. With the PostgreSQL-based planner (optimizer = off), window aggregation uses a sort-based plan and this parameter has no effect.

A vectorized window aggregate (WindowHashAgg) over a large partition set can exceed available memory and cause out-of-memory failures. The vector.winagg_spill_memory_mb parameter controls the memory budget for window aggregation. When the estimated working memory exceeds this budget, data is automatically spilled to disk and processed in partitions, preventing out-of-memory failures on large window queries.

The default value is 512 MB, which is suitable for most workloads. You can increase this value if your system has sufficient memory and you want to reduce disk spills, or decrease it to conserve memory in resource-constrained environments. Setting it to 0 disables spill-to-disk entirely. Because this budget is independent of the hash join spill budget, you can size window aggregate spill separately from other operators in the same query.

-- Set the window aggregate spill memory budget to 1024 MB.
SET vector.winagg_spill_memory_mb = 1024;

-- Disable spill-to-disk (not recommended for large window queries).
SET vector.winagg_spill_memory_mb = 0;

Use vectorized scans on foreign tables

ORC and Parquet foreign tables created with datalake_fdw can use vectorized scans. Enable vectorization in the session; the foreign table itself needs no extra setting:

SET vector.enable_vectorization = on;

Foreign tables in other formats, including CSV, text, and Avro, stay on the row engine and raise no error.

Supported column types

A query is vectorized when every column it references has one of these types: smallint, integer, bigint, real, double precision, numeric, date, timestamp, text, varchar, char, and boolean.

A numeric column has two further requirements:

  • A numeric column in a Parquet foreign table must declare its precision and scale. Without them, the table creation itself fails with The precision of numeric in foreign tables with parquet format should be specified explicitly.

  • The precision cannot exceed 35.

When a query references a column outside that range, such as a timestamptz column, the whole query falls back to the row engine. The result stays the same, only without the speedup. The decision covers the columns the query actually references, so a table that holds an unsupported column can still be scanned with vectorization as long as the query leaves that column out.

Confirm whether a foreign table query is vectorized

The scan node of a foreign table always appears as Foreign Scan in the EXPLAIN output, without a Vec prefix. Read the nodes above it instead:

gpadmin=# EXPLAIN SELECT count(*) FROM sales_parquet WHERE amount > 100;
                                       QUERY PLAN
----------------------------------------------------------------------------------------
 Vec Aggregate  (cost=0.00..431.00 rows=1 width=8)
   ->  Vec Gather Motion 2:1  (slice1; segments: 2)  (cost=0.00..431.00 rows=1 width=1)
         ->  Foreign Scan on sales_parquet  (cost=0.00..431.00 rows=1 width=1)
               Filter: (amount > 100)
 Optimizer: GPORCA
(5 rows)

To see why a query falls back to the row engine, turn on the fallback log in the session:

SET vector.print_fallback_log = on;
SET client_min_messages = log;

The server then prints one log line per fallback reason when you run the query, for example Fallback to non-vectorization: numeric type precision 38 not support.

Restrictions on numeric columns in ORC foreign tables

Warning

With vectorization on, a numeric column in an ORC foreign table returns wrong values. The decimal places are dropped and the value is scaled up, so a column with scale 2 reads 0.01 as 1. The query raises no error, and both filter conditions and aggregate results are wrong. A numeric column with scale 0 is unaffected, and so are Parquet foreign tables.

Turn vectorization off in the session before you query a numeric column with a scale above 0 in an ORC foreign table. To use vectorization on such data, store it as Parquet instead.

Also, when the ORC file stores a numeric column with a precision above 35 and the foreign table declares the column with a precision of 35 or less, the segment process crashes once vectorization is on, which interrupts every session on that segment. Declare the column with the precision the file actually uses.

Verify whether a query is vectorized

You can use EXPLAIN to check whether a query is a vectorization query.

  • If the first line of the QUERY PLAN has a “Vec” label, it means the query uses vectorization.

    gpadmin=# EXPLAIN SELECT * FROM int8_tbl;
                                                    QUERY PLAN
    -----------------------------------------------------------------------------------
    Vec Gather Motion 3:1  (slice1; segments: 3)  (cost=0.00..431.00 rows=1 width=16)
    ->  Vec Seq Scan on int8_tbl  (cost=0.00..431.00 rows=1 width=16)
    Optimizer: Pivotal Optimizer (GPORCA)
    (3 rows)
    
  • If the first line of the QUERY PLAN does not have a “Vec” label, it means the query does not use vectorization.

    gpadmin=# EXPLAIN SELECT * FROM int8_tbl;
                                   QUERY PLAN                                   
    -------------------------------------------------------------------------------
    Gather Motion 3:1  (slice1; segments: 3)  (cost=0.00..431.00 rows=1 width=16)
    ->  Seq Scan on int8_tbl  (cost=0.00..431.00 rows=1 width=16)
    Optimizer: Pivotal Optimizer (GPORCA)
    (3 rows)
    

Features supported by vectorization

Feature

Supported or not

Description

Storage format

Supported

AOCS, PAX, and ORC and Parquet foreign tables created with datalake_fdw

Storage format

Not supported

HEAP

Data types

Supported

int2, int4, int8, float8, bool, char, tid, date, time, timestamp, timestamptz, varchar, text, numeric

Data types

Not supported

Custom type

Scan operator

Supported

Scanning AOCS tables, PAX tables, and ORC and Parquet foreign tables, complex filter conditions

Scan operator

Not supported

HEAP tables

Agg operator

Supported

Aggregate functions: min, max, count, sum, avg, stddev_samp (integer columns only) Aggregation plans: PlanAggregate (simple aggregate), GroupAggregate (sorted aggregate), HashAggregate (hash aggregate)

Agg operator

Not Supported

Aggregate functions: stddev, stddev_pop (standard deviation), stddev_samp on columns of types other than integer, variance, var_samp, var_pop (variance) Aggregation plans: MixedAggregate (mixed aggregate)

Limit operator

Supported

All

ForeignScan operator

Supported

All

Result operator

Supported

All

Append operator

Supported

All

Subquery operator

Supported

All

Sequence operator

Supported

All

NestedLoopJoin operator

Supported

Join types: inner join, left join, semi join, anti join

NestedLoopJoin operator

Not supported

Join types: right join, full join, semi-anti join Join conditions: different data types, complex inequality conditions

Material operator

Supported

All

ShareInputScan operator

Supported

All

ForeignScan operator

Supported

All

HashJoin operator

Supported

Join types: inner join, left join, right join, full join, semi join, anti join, semi-anti join

HashJoin operator

Not Supported

Join conditions: different data types, complex inequality conditions

Sort operator

Supported

Sorting order: ascending, descending algorithms: order by, order by limit

WindowAgg operator

Supported

Supports sort-based window aggregation (WindowAgg) and hash-based window aggregation (WindowHashAgg). For how the optimizer chooses between them, see Execution methods for window functions.

Motion operator

Supported

GATHER (sending tuples from multiple senders to one receiver), GATHER_SINGLE (single-node gathering), HASH (simple hash conditions), BROADCAST (broadcast gathering), EXPLICIT (explicit gathering)

Motion operator

Not supported

Hash gathering (complex hash conditions)

Expressions

Supported

case when, is distinct, is not distinct, grouping, groupid, abs, round, upper, textcat, date_pli, coalesce, substr, date_trunc

date_trunc function

Supported

date_trunc(unit, timestamp) runs through the vectorized engine when the column is of type timestamp and unit is a constant string literal matching one of: nanosecond, microsecond, millisecond, second, minute, hour, day, month, quarter, year. Unit names are case-insensitive, and plural forms (for example, days, months) are accepted.

date_trunc function

Not supported

The query falls back to row-based execution, with identical results, when any of the following applies: the unit argument is not a constant; unit is week, millennium, century, or decade; the second argument is of type timestamptz or date.

Bench

Supported

ClickHouse, TPC-H, TPC-DS, ICW, ICW-ORCA

Threaded execution on single node

By performing threaded parallel computation within the execution node, better utilization of multi-core machine resources can be achieved, reducing query time and improving query performance. The following vectorized operators support threaded acceleration:

  • Scan (Filter)

  • Join

  • Agg

  • Sort

Currently, the vectorized query feature supports enabling threaded queries in a single-node deployment. To enable it, the following GUC values need to be configured:

set vector.enable_vectorization=on;  -- Enable vectorized query
set vector.enable_plan_merge=on; -- Enable Pipeline scheduling execution
set vector.pool_threads=8;  -- Configure the number of execution threads

Setting vector.pool_threads to 1 is treated as 0, which disables the thread pool. A one-thread pool has no second thread to drain its own queue, so that value is not usable. Set it to at least 2 for threaded execution.

Vectorized threaded execution relies on the underlying PAX storage file format. Take the tpcds PAX table store_sales as an example. When threaded execution is not enabled:

gpadmin=# set vector.pool_threads=0;
SET
gpadmin=# explain analyse select ss_quantity from store_sales 
where ss_quantity between 6 and 10 and ss_list_price between 5 and 5+10;
                                                                                    QUERY PLAN

------------------------------------------------------------------------------------------------------------
---------------------------------
Vec Seq Scan on store_sales  (cost=0.00..661957.82 rows=28801 width=4) (actual time=8804.432..8805.236 rows
=94117 loops=1)
    Filter: ((ss_quantity >= 6) AND (ss_quantity <= 10) AND (ss_list_price >= '5'::numeric(15,2)) AND (ss_lis
t_price <= '15'::numeric(15,2)))
Planning Time: 0.475 ms
    (slice0)    Executor memory: 114K bytes.
Memory used:  128000kB
Optimizer: Postgres query optimizer
Execution Time: 8809.841 ms
(7 rows)

When threaded execution is enabled and the pool_threads parameter is set to 8:

gpadmin=# set vector.pool_threads=8;
SET
gpadmin=# explain analyse select ss_quantity from store_sales 
where ss_quantity between 6 and 10 and ss_list_price between 5 and 5+10;
                                                                                    QUERY PLAN

------------------------------------------------------------------------------------------------------------
---------------------------------

After enabling threaded queries, the query time is significantly reduced.

Performance evaluation

TPC-H

There are 22 query SQL statements in TPC-H. Compared to non-vectorized execution, vectorization improves the overall performance by 2 times or more. For pure aggregation SQL statements, vectorization can speed up the process by 3 times or more compared to non-vectorized execution.

Statement

Query time without vec (s)

Query time with vec (s)

Time diff (s) = non-vec - vec

Improvement (times)

1

54

18

36

3

2

9

3

6

3

3

23

14

9

1.64

4

44

22

22

2

5

28

11

17

2.54

6

10

7

3

1.43

7

22

13

9

1.69

8

28

11

17

2.55

9

62

21

41

2.95

10

22

12

10

1.83

11

5

5

0

1

12

15

11

4

1.36

13

28

13

15

2.15

14

10

7

3

1.43

15

12

7

5

1.71

16

7

4

3

1.75

17

92

20

72

4.6

18

79

20

59

3.95

19

13

13

0

1

20

23

13

10

1.77

21

72

61

11

1.15

22

18

18

0

1

Total

676

324

352

2.09

TPC-DS

TPC-DS has 99 query SQL statements, tested in a 1T data environment. Compared to non-vectorized execution, vectorization improves the overall performance by 2 times or more.

Statement

Query time without vec (s)

Query time with vec (s)

Time diff (s) = non-vec - vec

Improvement (times)

1

5

2

3

2.50

2

10

4

6

2.50

3

5

2

3

2.50

4

41

19

22

2.16

5

11

11

0

1.00

6

10

4

6

2.50

7

12

4

8

3.00

8

13

8

5

1.63

9

11

7

4

1.57

10

12

5

7

2.40

11

30

14

16

2.14

12

3

2

1

1.50

13

10

6

4

1.67

14

46

37

9

1.24

15

10

2

8

5.00

16

13

7

6

1.86

17

13

5

8

2.60

18

15

5

10

3.00

19

12

4

8

3.00

20

3

2

1

1.50

21

6

1

5

6.00

22

14

5

9

2.80

23

125

43

82

2.91

24

147

60

87

2.45

25

13

4

9

3.25

26

7

2

5

3.50

27

11

5

6

2.20

28

7

6

1

1.17

29

12

4

8

3.00

30

11

3

8

3.67

31

13

6

7

2.17

32

7

2

5

3.50

33

10

5

5

2.00

34

11

4

7

2.75

35

16

5

11

3.20

36

10

4

6

2.50

37

6

3

3

2.00

38

23

7

16

3.29

39

25

22

3

1.14

40

5

2

3

2.50

41

1

1

0

1.00

42

5

2

3

2.50

43

7

3

4

2.33

44

4

4

0

1.00

45

12

3

9

4.00

46

17

6

11

2.83

47

13

7

6

1.86

48

8

5

3

1.60

49

7

6

1

1.17

50

11

3

8

3.67

51

11

18

-7

0.61

52

6

2

4

3.00

53

7

3

4

2.33

54

20

16

4

1.25

55

6

2

4

3.00

56

9

7

2

1.29

57

8

4

4

2.00

58

7

4

3

1.75

59

19

9

10

2.11

60

9

6

3

1.50

61

13

5

8

2.60

62

4

2

2

2.00

63

7

3

4

2.33

64

28

13

15

2.15

65

18

7

11

2.57

66

5

3

2

1.67

67

489

205

284

2.39

68

16

8

8

2.00

69

10

4

6

2.50

70

17

16

1

1.06

71

9

4

5

2.25

72

80

47

33

1.70

73

11

4

7

2.75

74

25

9

16

2.78

75

20

13

7

1.54

76

4

4

0

1.00

77

9

5

4

1.80

78

29

12

17

2.42

79

16

6

10

2.67

80

11

7

4

1.57

81

10

3

7

3.33

82

10

4

6

2.50

83

3

3

0

1.00

84

8

2

6

4.00

85

6

3

3

2.00

86

4

2

2

2.00

87

24

7

17

3.43

88

21

8

13

2.63

89

7

4

3

1.75

90

4

1

3

4.00

91

6

2

4

3.00

92

6

1

5

6.00

93

11

3

8

3.67

94

8

5

3

1.60

95

66

24

42

2.75

96

7

2

5

3.50

97

14

5

9

2.80

98

6

3

3

2.00

99

7

3

4

2.33

Total

000

916

/

2.18