[Mesa-dev] [PATCH v2 28/37] panfrost: Start tracking inter-batch dependencies
Alyssa Rosenzweig
alyssa at rosenzweig.io
Mon Sep 16 20:15:14 UTC 2019
> + * A BO is either being written or read at any time, that's what the type field
> + * encodes.
Might this be inferred from (writer != NULL) and (readers->length > 0)?
> + util_dynarray_foreach(&batch->dependencies,
> + struct panfrost_batch_fence *, dep)
> + panfrost_batch_fence_unreference(*dep);
> +
Nit: Wrap in { braces } for 2+ lines for clarity
> +static void
> +panfrost_batch_add_dep(struct panfrost_batch *batch,
> + struct panfrost_batch_fence *newdep)
> +{
> + if (batch == newdep->batch)
> + return;
> +
> + util_dynarray_foreach(&batch->dependencies,
> + struct panfrost_batch_fence *, dep) {
> + if (*dep == newdep)
> + return;
> + }
> +
> + /* Make sure the dependency graph is acyclic. */
> + assert(!panfrost_dep_graph_contains_batch(newdep->batch, batch));
> +
> + panfrost_batch_fence_reference(newdep);
> + util_dynarray_append(&batch->dependencies,
> + struct panfrost_batch_fence *, newdep);
> +
> + /* We now have a batch depending on us, let's make sure new draw/clear
> + * calls targeting the same FBO use a new batch object.
> + */
> + if (newdep->batch)
> + panfrost_freeze_batch(newdep->batch);
> +}
I'm wondering if batch->dependencies should be expressed as a set,
rather than a dynarray, such that testing whether a batch has a
given dependency is ideally O(1), not O(N).
In practice I don't know if the asymptotic complexity matters, esp. for
small numbers of batches, and in practice hash table iteration is slow
enough in mesa* that maybe it would be counterproductive.
Just something I thought I might throw out there.
* Set iteration ought to be no slower than array iteration, but constant
factors are a thing.
> + int ret = drmSyncobjWait(pan_screen(fence->ctx->base.screen)->fd,
> + &fence->syncobj, 1, 0, 0, NULL);
> +
> + fence->signaled = ret >= 0;
> + return fence->signaled;
> +}
Nit: Add a "/* Cache whether the fence was signaled */" comment?
> +static void
> +panfrost_bo_access_gc_fences(struct panfrost_context *ctx,
> + struct panfrost_bo_access *access,
> + const struct panfrost_bo *bo)
Could you remind me what gc abbreviates? Sorry.
I'm a little unclear on what the function's purpose is based on the
name.
> +{
> + if (access->writer && panfrost_batch_fence_is_signaled(access->writer)) {
> + panfrost_batch_fence_unreference(access->writer);
> + access->writer = NULL;
> + }
Spacing.
> +
> + unsigned nreaders = 0;
> + util_dynarray_foreach(&access->readers, struct panfrost_batch_fence *,
> + reader) {
> + if (!*reader)
> + continue;
Please add parens (!(*reader)) for clarity.
> +static void
> +panfrost_gc_fences(struct panfrost_context *ctx)
> +{
> + hash_table_foreach(ctx->accessed_bos, entry) {
> + struct panfrost_bo_access *access = entry->data;
> +
> + assert(access);
> + panfrost_bo_access_gc_fences(ctx, access, entry->key);
> + if (!util_dynarray_num_elements(&access->readers,
> + struct panfrost_batch_fence *) &&
> + !access->writer)
> + _mesa_hash_table_remove(ctx->accessed_bos, entry);
> + }
> +}
Question: is it safe to remove entries while iterating the table?
(Not a review comment, I don't know the details of mesa's
implementation)
Also not clear what panfrost_gc_fences is for.
> +static void
> +panfrost_batch_update_bo_access(struct panfrost_batch *batch,
> + struct panfrost_bo *bo, uint32_t access_type)
> +{
> + struct panfrost_context *ctx = batch->ctx;
> + struct panfrost_bo_access *access;
> + uint32_t old_access_type;
> + struct hash_entry *entry;
> +
> + assert(access_type == PAN_BO_GPU_ACCESS_WRITE ||
> + access_type == PAN_BO_GPU_ACCESS_READ);
> +
> + entry = _mesa_hash_table_search(ctx->accessed_bos, bo);
> + access = entry ? entry->data : NULL;
> + if (access) {
> + old_access_type = access->type;
> + } else {
> + access = rzalloc(ctx, struct panfrost_bo_access);
> + util_dynarray_init(&access->readers, access);
> + _mesa_hash_table_insert(ctx->accessed_bos, bo, access);
> + /* We are the first to access this BO, let's initialize
> + * old_access_type to our own access type in that case.
> + */
> + old_access_type = access_type;
> + access->type = access_type;
> + }
> +
> + assert(access);
> +
> + if (access_type == PAN_BO_GPU_ACCESS_WRITE &&
> + old_access_type == PAN_BO_GPU_ACCESS_READ) {
> + /* Previous access was a read and we want to write this BO.
> + * We first need to add explicit deps between our batch and
> + * the previous readers.
> + */
> + util_dynarray_foreach(&access->readers,
> + struct panfrost_batch_fence *, reader) {
> + /* We were already reading the BO, no need to add a dep
> + * on ourself (the acyclic check would complain about
> + * that).
> + */
> + if (!*reader || (*reader)->batch == batch)
!(*reader)
> + continue;
> +
> + panfrost_batch_add_dep(batch, *reader);
> + }
> + panfrost_batch_fence_reference(batch->out_sync);
Nit: Add a blank line between
> + } else if (access_type == PAN_BO_GPU_ACCESS_READ &&
> + old_access_type == PAN_BO_GPU_ACCESS_WRITE) {
> + /* Previous access was a write and we want to read this BO.
> + * First check if we were the previous writer, in that case
> + * we want to keep the access type unchanged, as a write is
> + * more constraining than a read.
> + */
> + if (access->writer != batch->out_sync) {
> + /* Add a dependency on the previous writer. */
> + panfrost_batch_add_dep(batch, access->writer);
> +
> + /* The previous access was a write, there's no reason
> + * to have entries in the readers array.
> + */
> + assert(!util_dynarray_num_elements(&access->readers,
> + struct panfrost_batch_fence *));
> +
> + /* Add ourselves to the readers array. */
> + panfrost_batch_fence_reference(batch->out_sync);
> + util_dynarray_append(&access->readers,
> + struct panfrost_batch_fence *,
> + batch->out_sync);
> + access->type = access_type;
I would prefer an explicit `access->type = PAN_BO_GPU_ACCESS_READ`, so
the parallelism with `readers` is visually clear without needing to
glance back up at the condition.
> + }
> + } else {
> + /* Previous access was a read and we want to read this BO.
> + * Add ourselves to the readers array if we're not already
> + * there and add a dependency on the previous writer (if any).
> + */
> + bool already_there = false;
Spacing.
> + util_dynarray_foreach(&access->readers,
> + struct panfrost_batch_fence *, reader) {
> + if (*reader && (*reader)->batch == batch) {
> + already_there = true;
> + break;
> + }
> + }
This raises the point that access->readers should probably also be a
set, not an array, but again, not sure how much it matters.
> - if (ctx->last_out_sync) {
> + if (reqs & PANFROST_JD_REQ_FS && batch->first_job.gpu)
Could we add a `bool is_fragment` with this condition and then make this
as well as the if below simple if (is_fragment)?
> + /* Submit the dependencies first. */
> + util_dynarray_foreach(&batch->dependencies,
> + struct panfrost_batch_fence *, dep) {
> + if ((*dep)->batch)
> + panfrost_batch_submit((*dep)->batch);
> + }
I assume this logic will be refined later in the series...?
More information about the mesa-dev
mailing list