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

Improve get_coordinates! #536

Merged
merged 4 commits into from
Nov 15, 2022
Merged

Improve get_coordinates! #536

merged 4 commits into from
Nov 15, 2022

Conversation

kimauth
Copy link
Member

@kimauth kimauth commented Nov 13, 2022

Small improvements on the definition of getcoordinates!. In particular:

  • Compatible with AbstractGrid interface . See the SmallGrid example from the docs, that doesn't need to define cellcoords! anymore
  • Reduce code duplication. There is getcoordinates! working on grids and cellcoords! working on dofhandlers (but also on grids...). For DofHandler both fall back to one definition with this PR. We should deprecate one of them in the future.
  • Slightly better performance for coordinate queries on mixed grids. See micro-benchmarks below.

Coordinate query benchmarks

This PR is part of making an effort to join the two different dofhandlers. Therefore, here are some interesting performance investigations on querying coordinates:

Regular grid

The difference on a standard non-mixed grid is minor:

Regular Grid #PR #master
getcoordinates!(xe, grid, cellid) 4.291 ns (0 allocations: 0 bytes) 4.584 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid) 4.250 ns (0 allocations: 0 bytes) 4.875 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid) 4.291 ns (0 allocations: 0 bytes) 5.208 ns (0 allocations: 0 bytes)

Mixed grid

The PR performs much better than master on querying coordinates directly from the grid for a cell vector of type AbstractCell. However, that is still about twice as slow compared to querying coordinates from the MixedDofHandler.

AbstractCell #PR #master
getcoordinates!(xe, grid, cellid) 44.147 ns (0 allocations: 0 bytes) 1.262 μs (20 allocations: 704 bytes)
Ferrite.cellcoords!(xe, grid, cellid) 43.875 ns (0 allocations: 0 bytes) 1.2 μs (14 allocations: 464 bytes)
Ferrite.cellcoords!(xe, dh, cellid) 20.687 ns (0 allocations: 0 bytes) 20.687 ns (0 allocations: 0 bytes)

Notice:
For mixed grids (and thus MixedDofHandler) cellcoords!(xe, dh, cellid) is unchanged between the PR and master.

The more interesting result is that when using a union type for the cell vector instead of AbstractCell, the coordinate query from the grid is almost as fast as for a non-mixed grid and faster than querying the coordinates from the MixedDofHandler. (In fact, this has been the case even on master.)

I got similar results for grids with 4 and 5 different cell types, compare the benchmark code below. This surprised me, because I thought this worked well due to union splitting, which seems to cover up to 4 types. Perhaps someone can explain that. 🙂

Union{Quadrilateral, Triangle, Line} #PR #master
getcoordinates!(xe, grid, cellid) 4.583 ns (0 allocations: 0 bytes) 5.583 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid) 5.541 ns (0 allocations: 0 bytes) 7.083 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid) 20.645 ns (0 allocations: 0 bytes) 20.687 ns (0 allocations: 0 bytes)

Take away message:
As long as the cell vector is well typed, there seems to be no need for a specialized coordinate query for mixed grids (at least up to 5 different cell types). That would mean we could remove the grid representation from the MixedDofHandler and have it fall back to querying the coordinates directly from its grid as well. That would be a major step towards joining the dofhandlers.

Benchmark code
using Ferrite
using BenchmarkTools

xe = Vector{Vec{2,Float64}}(undef, 4)

# concrete grid
grid = generate_grid(Quadrilateral, (10, 10))
dh = DofHandler(grid)
push!(dh, :u, 2)
close!(dh)

cellid = 57

println("Regular Grid:")
print("getcoordinates(xe, grid, cellid): ");
@btime getcoordinates!($xe, $grid, $cellid) 
print("Ferrite.cellcoords!(xe, grid, cellid): ");
@btime Ferrite.cellcoords!($xe, $grid, $cellid) 
print("Ferrite.cellcoords!(xe, dh, cellid): ");
@btime Ferrite.cellcoords!($xe, $dh, $cellid) 

##############################################################
# Performance for mixed grids
##############################################################
# grid with AbstractCell vector
union3_cells = Union{Quadrilateral, Triangle, Line}[c for c in vcat(grid.cells, [Triangle(cell.nodes[1:3]) for cell in grid.cells], [Line(cell.nodes[1:2]) for cell in grid.cells])]
abstract_cells = Ferrite.AbstractCell[c for c in union3_cells]
grid_abstract = Grid(abstract_cells, grid.nodes)
dh_abstract = MixedDofHandler(grid_abstract)
field = Field(:u, Lagrange{2,RefCube,1}(), 2)
push!(dh_abstract, FieldHandler([field], Set(1:getncells(grid))))
close!(dh_abstract)

println()
println("Mixed Grid, AbstractCell")
print("getcoordinates(xe, grid, cellid): ");
@btime getcoordinates!($xe, $grid_abstract, $cellid) 
print("Ferrite.cellcoords!(xe, grid, cellid): ");
@btime Ferrite.cellcoords!($xe, $grid_abstract, $cellid) 
print("Ferrite.cellcoords!(xe, dh, cellid): ");
@btime Ferrite.cellcoords!($xe, $dh_abstract, $cellid)

# grid with 3 cell types
union3_cells = Union{Quadrilateral, Triangle, Line}[c for c in vcat(grid.cells, [Triangle(cell.nodes[1:3]) for cell in grid.cells], [Line(cell.nodes[1:2]) for cell in grid.cells])]
grid_union3 = Grid(union3_cells, grid.nodes)
dh_union3 = MixedDofHandler(grid_union3)
field = Field(:u, Lagrange{2,RefCube,1}(), 2)
push!(dh_union3, FieldHandler([field], Set(1:getncells(grid))))
close!(dh_union3)

println()
println("Mixed Grid, Union{Quadrilateral, Triangle, Line}")
print("getcoordinates(xe, grid, cellid): ");
@btime getcoordinates!($xe, $grid_union3, $cellid)
print("Ferrite.cellcoords!(xe, grid, cellid): ");
@btime Ferrite.cellcoords!($xe, $grid_union3, $cellid)
print("Ferrite.cellcoords!(xe, dh, cellid): ");
@btime Ferrite.cellcoords!($xe, $dh_union3, $cellid); 


# grid with 4 cell types
union4_cells = Union{Quadrilateral, Triangle, Line, QuadraticLine}[c for c in vcat(
                    grid.cells,
                    [Triangle(cell.nodes[1:3]) for cell in grid.cells],
                    [Line(cell.nodes[1:2]) for cell in grid.cells],
                    [QuadraticLine(cell.nodes[1:3]) for cell in grid.cells],
                   )]
grid_union4 = Grid(union4_cells, grid.nodes)
dh_union4 = MixedDofHandler(grid_union4)
field = Field(:u, Lagrange{2,RefCube,1}(), 2)
push!(dh_union4, FieldHandler([field], Set(1:getncells(grid))))
close!(dh_union4)

println()
println("Mixed Grid, Union{Quadrilateral, Triangle, Line, Quadratic}")
print("getcoordinates(xe, grid, cellid): ");
@btime getcoordinates!($xe, $grid_union4, $cellid)
print("Ferrite.cellcoords!(xe, grid, cellid): ");
@btime Ferrite.cellcoords!($xe, $grid_union4, $cellid)
print("Ferrite.cellcoords!(xe, dh, cellid): ");
@btime Ferrite.cellcoords!($xe, $dh_union4, $cellid); 

# ?? Union splitting only does up to 4 types: https://github.com/JuliaLang/julia/blob/3510eeb4c99b6daa42a27d0f7d96be8af21067ac/base/compiler/types.jl#L155
# grid with 5 cell types
union5_cells = Union{Quadrilateral, Triangle, Line, QuadraticLine, Line2D}[c for c in vcat(
                    grid.cells,
                    [Triangle(cell.nodes[1:3]) for cell in grid.cells],
                    [Line(cell.nodes[1:2]) for cell in grid.cells],
                    [QuadraticLine(cell.nodes[1:3]) for cell in grid.cells],
                    [Line2D(cell.nodes[1:2]) for cell in grid.cells],
                   )]
grid_union5 = Grid(union5_cells, grid.nodes)
dh_union5 = MixedDofHandler(grid_union5)
field = Field(:u, Lagrange{2,RefCube,1}(), 2)
push!(dh_union5, FieldHandler([field], Set(1:getncells(grid))))
close!(dh_union5)

println()
println("Mixed Grid, Union{Quadrilateral, Triangle, Line, Quadratic, Line2D}")
print("getcoordinates(xe, grid, cellid): ");
@btime getcoordinates!($xe, $grid_union5, $cellid)
print("Ferrite.cellcoords!(xe, grid, cellid): ");
@btime Ferrite.cellcoords!($xe, $grid_union5, $cellid)
print("Ferrite.cellcoords!(xe, dh, cellid): ");
@btime Ferrite.cellcoords!($xe, $dh_union5, $cellid);  

@codecov-commenter
Copy link

codecov-commenter commented Nov 13, 2022

Codecov Report

Base: 92.56% // Head: 92.56% // Increases project coverage by +0.00% 🎉

Coverage data is based on head (8f3d18d) compared to base (664d0b7).
Patch coverage: 100.00% of modified lines in pull request are covered.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #536   +/-   ##
=======================================
  Coverage   92.56%   92.56%           
=======================================
  Files          22       22           
  Lines        3900     3901    +1     
=======================================
+ Hits         3610     3611    +1     
  Misses        290      290           
Impacted Files Coverage Δ
src/Dofs/DofHandler.jl 88.84% <ø> (-0.33%) ⬇️
src/Grid/grid.jl 87.30% <100.00%> (+0.36%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

Copy link
Member

@termi-official termi-official left a comment

Choose a reason for hiding this comment

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

Cool addition! Thanks for tackling this. I left some minor comments.

docs/src/manual/grid.md Outdated Show resolved Hide resolved
src/Grid/grid.jl Outdated Show resolved Hide resolved
src/Grid/grid.jl Show resolved Hide resolved
@KristofferC
Copy link
Collaborator

This surprised me, because I thought this worked well due to union splitting, which seems to cover up to 4 types. Perhaps someone can explain that.

I think if you have an explicit Union it will go higher. It is only when a union is formed "implicitly" in type inference that it will have a limit. This was changed at some point but I can't really find the PR.

@kimauth
Copy link
Member Author

kimauth commented Nov 14, 2022

This surprised me, because I thought this worked well due to union splitting, which seems to cover up to 4 types. Perhaps someone can explain that.

I think if you have an explicit Union it will go higher. It is only when a union is formed "implicitly" in type inference that it will have a limit. This was changed at some point but I can't really find the PR.

Hearing that makes me less suspicious about my benchmark results. 😅 It also makes a strong point for actually removing the coordinate representation from the MixedDofHandler.

@lijas
Copy link
Collaborator

lijas commented Nov 14, 2022

I think we can remove the coordinate representation in the MixedDofhandler in this PR then :). I have not used @deprecate in julia before, but I think we can add @deprecate cellcoords!(x, dh, cellid) getcoordinates!(x, dh.grid, cellid). It is a simple update in the either way.

Copy link
Member

@koehlerson koehlerson left a comment

Choose a reason for hiding this comment

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

Very nice changes! Looking forward to have the DofHandlers merged :)

PR

Regular Grid:
getcoordinates(xe, grid, cellid):   9.023 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   9.033 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   9.034 ns (0 allocations: 0 bytes)

Mixed Grid, AbstractCell
getcoordinates(xe, grid, cellid):   27.389 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   28.400 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   9.371 ns (0 allocations: 0 bytes)

Mixed Grid, Union{Quadrilateral, Triangle, Line}
getcoordinates(xe, grid, cellid):   9.705 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   10.712 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   9.370 ns (0 allocations: 0 bytes)

Mixed Grid, Union{Quadrilateral, Triangle, Line, Quadratic}
getcoordinates(xe, grid, cellid):   10.037 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   11.363 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   9.369 ns (0 allocations: 0 bytes)

Mixed Grid, Union{Quadrilateral, Triangle, Line, Quadratic, Line2D}
getcoordinates(xe, grid, cellid):   11.056 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   12.695 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   9.120 ns (0 allocations: 0 bytes)

Master

Regular Grid:
getcoordinates(xe, grid, cellid):   9.656 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   10.425 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   10.882 ns (0 allocations: 0 bytes)

Mixed Grid, AbstractCell
getcoordinates(xe, grid, cellid):   2.504 μs (20 allocations: 704 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   2.755 μs (14 allocations: 464 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   10.814 ns (0 allocations: 0 bytes)

Mixed Grid, Union{Quadrilateral, Triangle, Line}
getcoordinates(xe, grid, cellid):   12.197 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   18.511 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   10.499 ns (0 allocations: 0 bytes)

Mixed Grid, Union{Quadrilateral, Triangle, Line, Quadratic}
getcoordinates(xe, grid, cellid):   12.138 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   19.279 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   10.812 ns (0 allocations: 0 bytes)

Mixed Grid, Union{Quadrilateral, Triangle, Line, Quadratic, Line2D}
getcoordinates(xe, grid, cellid):   15.337 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, grid, cellid):   20.822 ns (0 allocations: 0 bytes)
Ferrite.cellcoords!(xe, dh, cellid):   10.503 ns (0 allocations: 0 bytes)

@inbounds for i in 1:length(x)
x[i] = grid.nodes[grid.cells[cell].nodes[i]].x
x[i] = getcoordinates(getnodes(grid, cell.nodes[i]))
Copy link
Member

Choose a reason for hiding this comment

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

This is not for this PR, but in general a Cell Interface function to return the nodes would be nice, like getnodes(cell::AbstractCell,i::Integer) I think there is none currently

Copy link
Member

Choose a reason for hiding this comment

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

There are indeed a few things which can be improved regarding the element and mesh interface, but I would leave them to separate PRs.

@termi-official
Copy link
Member

Do you think it makes sense to put some information of this PR into the docs into a performance section?

@kimauth
Copy link
Member Author

kimauth commented Nov 15, 2022

Do you think it makes sense to put some information of this PR into the docs into a performance section?

Mentioning that the cell vector in a Grid should be typed with an as tight as possible Union would probably be nice. But other than that I don't think this is of particular importance to the users, coordinate queries don't seem like something that is usually that performance relevant.

@KnutAM
Copy link
Member

KnutAM commented Nov 15, 2022

Do you think it makes sense to put some information of this PR into the docs into a performance section?

Mentioning that the cell vector in a Grid should be typed with an as tight as possible Union would probably be nice. But other than that I don't think this is of particular importance to the users, coordinate queries don't seem like something that is usually that performance relevant.

Perhaps also worth mentioning that a half-typed Cell (e.g. Cell{2}) doesn't improve performance in contrast to a Union of specific types (I thought that earlier)
(When changing FerriteMeshParser based on this PR's findings, my timings showed that using promote_type giving e.g. Cell{2} (as I did) was even slower than AbstractCell... I don't understand why an am a bit skeptical about my benchmark but it was great from this PR to see how much better the Union is!)

@kimauth
Copy link
Member Author

kimauth commented Nov 15, 2022

I'll leave the deprecation for a future PR, as there are more things to deprecate on the way to merging the dofhandlers. That leaves this PR as purely changing internals.

@kimauth kimauth merged commit c90c188 into master Nov 15, 2022
@kimauth kimauth deleted the ka/improve_getcoordinates branch November 15, 2022 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants