Skip to content

Commit ab4c196

Browse files
P4 Infra Teamcopybara-github
authored andcommitted
p4_pdpi: avoid copies on the PacketIn/PacketOut translation hot path.
PiPacketIoToIr / IrPacketIoToPd no longer copy the metadata definition maps or the metadata definitions, only format error strings on the error path, and move the payload into the PD message instead of copying it. PiperOrigin-RevId: 991222288
1 parent 0c5601f commit ab4c196

6 files changed

Lines changed: 136 additions & 97 deletions

File tree

‎README.md‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
[![build](https://github.com/google/p4-infra/actions/workflows/ci.yml/badge.svg)](https://github.com/google/p4-infra/actions/workflows/ci.yml)
1+
[![build](https://github.com/google/p4-infra/actions/workflows/ci.yml/badge.svg)]
2+
(https://github.com/google/p4-infra/actions/workflows/ci.yml)
23

34
# P4 Infrastructure
45

@@ -9,9 +10,9 @@ representation (IR) of program-dependent (PD) and program-independent (PI) and
910
the code to convert between the different representations. P4Runtime generally
1011
uses a program-independent representation (or PI) for P4 entities such as table
1112
entries, counters, etc. This is achieved by using numeric IDs instead of names.
12-
The downside of this is that the representation is hard to read by humans. In
13-
contrast, a program-dependent (or PD) representation uses names and is generally
14-
more friendly to humans.
13+
The downside of this is that the representation is hard to read by humans.
14+
In contrast, a program-dependent (or PD) representation uses names and is
15+
generally more friendly to humans.
1516

1617
The PacketLib library provides functionality to parse a raw byte string
1718
representation of a network packet and represent it as a collection of C++
@@ -27,13 +28,14 @@ This is not an officially supported Google product.
2728
The version of releases follows the year, month, date, and patch format.
2829

2930
1. Create a new branch with the name `release-yyyymmdd`.
30-
2. In the branch, the version in the MODULE.bazel must be updated to match
31-
`yyyymmdd.p`, where p is the patch number (initially 0).
31+
2. In the branch, the version in the MODULE.bazel
32+
must be updated to match `yyyymmdd.p`, where p is the patch number
33+
(initially 0).
3234
* Note that this branch should not be deleted to keep an archive of all
3335
releases and it makes it easier to add patches for a release.
3436
* Tip: You can do steps 1 and 2 in one swoop by editing MODULE.bazel
35-
directly in the browser and committing the change to a new branch of the
36-
correct name.
37+
directly in the browser and committing the change to a new branch of
38+
the correct name.
3739
3. Create a new release on Github with the tag being in the form `yyyymmdd.p`,
3840
the title being `P4 Infra yyyymmdd.p (month year)`, and the corresponding
3941
release notes (can be auto-generated).

‎p4_pdpi/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ automatically convert between them.
1414

1515
This includes sub-libraries like (incomplete list):
1616

17-
- [Reference Library](references.md): Validates dependencies expressed through
18-
@refers_to and @referenced_by annotations across P4 entities.
17+
- [Reference Library](references.md): Validates dependencies expressed
18+
through @refers_to and @referenced_by annotations across P4 entities.
1919

2020
This is a work in progress.
2121

‎p4_pdpi/ir.cc‎

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -848,46 +848,48 @@ absl::Status PiActionSetToIr(
848848
// Generic helper that works for both packet-in and packet-out. For both, I is
849849
// one of p4::v1::{PacketIn, PacketOut} and O is one of {IrPacketIn,
850850
// IrPacketOut}.
851+
//
852+
// This function is on the PacketIn/PacketOut hot path, so it avoids copying
853+
// the metadata definitions and only formats strings on the error path.
851854
template <typename I, typename O>
852-
StatusOr<O> PiPacketIoToIr(const IrP4Info& info, const std::string& kind,
855+
StatusOr<O> PiPacketIoToIr(const IrP4Info& info, absl::string_view kind,
853856
const I& packet, const TranslationOptions& options) {
854-
O result;
855-
result.set_payload(packet.payload());
856-
857-
const std::string& packet_description = absl::StrCat("'", kind, "' message");
858-
google::protobuf::Map<uint32_t, IrPacketIoMetadataDefinition> metadata_by_id;
857+
auto packet_description = [kind] {
858+
return absl::StrCat("'", kind, "' message");
859+
};
860+
const google::protobuf::Map<uint32_t, IrPacketIoMetadataDefinition>*
861+
metadata_by_id = nullptr;
859862
if (kind == "packet-in") {
860-
metadata_by_id = info.packet_in_metadata_by_id();
863+
metadata_by_id = &info.packet_in_metadata_by_id();
861864
} else if (kind == "packet-out") {
862-
metadata_by_id = info.packet_out_metadata_by_id();
865+
metadata_by_id = &info.packet_out_metadata_by_id();
863866
} else {
864867
return absl::InvalidArgumentError(GenerateFormattedError(
865-
packet_description,
868+
packet_description(),
866869
absl::StrCat(kNewBullet, "Invalid PacketIo type.")));
867870
}
868871

872+
O result;
873+
result.set_payload(packet.payload());
874+
result.mutable_metadata()->Reserve(packet.metadata().size());
875+
869876
std::vector<std::string> invalid_reasons;
870877
absl::flat_hash_set<uint32_t> used_metadata_ids(packet.metadata().size());
871878
for (const auto& metadata : packet.metadata()) {
872-
uint32_t id = metadata.metadata_id();
873-
const absl::Status& duplicate = gutil::InsertIfUnique(
874-
used_metadata_ids, id,
875-
absl::StrCat("Duplicate metadata found with ID ", id, "."));
876-
if (!duplicate.ok()) {
877-
invalid_reasons.push_back(absl::StrCat(kNewBullet, duplicate.message()));
879+
const uint32_t id = metadata.metadata_id();
880+
if (!used_metadata_ids.insert(id).second) {
881+
invalid_reasons.push_back(absl::StrCat(
882+
kNewBullet, "Duplicate metadata found with ID ", id, "."));
878883
continue;
879884
}
880885

881-
const auto& status_or_metadata_definition_ptr =
882-
gutil::FindPtrOrStatus(metadata_by_id, id);
883-
if (!status_or_metadata_definition_ptr.ok()) {
886+
const auto it = metadata_by_id->find(id);
887+
if (it == metadata_by_id->end()) {
884888
invalid_reasons.push_back(
885889
absl::StrCat(kNewBullet, " Metadata with ID ", id, " not defined."));
886890
continue;
887891
}
888-
889-
const pdpi::IrPacketIoMetadataDefinition metadata_definition =
890-
**status_or_metadata_definition_ptr;
892+
const pdpi::IrPacketIoMetadataDefinition& metadata_definition = it->second;
891893

892894
// Metadata with @padding annotation must be all zeros and must not be
893895
// included in IR representation.
@@ -900,35 +902,39 @@ StatusOr<O> PiPacketIoToIr(const IrP4Info& info, const std::string& kind,
900902
continue;
901903
}
902904

903-
IrPacketMetadata ir_metadata;
904905
const std::string& metadata_name = metadata_definition.metadata().name();
905-
ir_metadata.set_name(metadata_name);
906-
const absl::StatusOr<IrValue> ir_value = ArbitraryByteStringToIrValue(
906+
absl::StatusOr<IrValue> ir_value = ArbitraryByteStringToIrValue(
907907
metadata_definition.format(), metadata_definition.metadata().bitwidth(),
908908
metadata.value());
909909
if (!ir_value.ok()) {
910910
invalid_reasons.push_back(GenerateReason(MetadataName(metadata_name),
911911
ir_value.status().message()));
912912
continue;
913913
}
914-
*ir_metadata.mutable_value() = *ir_value;
915-
*result.add_metadata() = ir_metadata;
916-
}
917-
// Check for missing metadata
918-
for (const auto& item : metadata_by_id) {
919-
const auto& id = item.first;
920-
const auto& meta = item.second;
921-
if (!used_metadata_ids.contains(id)) {
922-
invalid_reasons.push_back(
923-
absl::StrCat(kNewBullet, "Metadata '", meta.metadata().name(),
924-
"' with id ", meta.metadata().id(), " is missing."));
925-
continue;
914+
IrPacketMetadata* ir_metadata = result.add_metadata();
915+
ir_metadata->set_name(metadata_name);
916+
*ir_metadata->mutable_value() = *std::move(ir_value);
917+
}
918+
// Check for missing metadata. If no errors occurred so far, then every ID in
919+
// `used_metadata_ids` is unique (duplicates are rejected above) and defined
920+
// in `metadata_by_id` (undefined IDs are rejected above), so
921+
// `used_metadata_ids` is a subset of the defined IDs. Matching sizes
922+
// therefore imply the two sets are equal, i.e. nothing is missing, and the
923+
// full scan below is only needed on the error path.
924+
if (used_metadata_ids.size() != metadata_by_id->size() ||
925+
!invalid_reasons.empty()) {
926+
for (const auto& [id, meta] : *metadata_by_id) {
927+
if (!used_metadata_ids.contains(id)) {
928+
invalid_reasons.push_back(
929+
absl::StrCat(kNewBullet, "Metadata '", meta.metadata().name(),
930+
"' with id ", meta.metadata().id(), " is missing."));
931+
}
926932
}
927933
}
928934

929935
if (!invalid_reasons.empty()) {
930936
return absl::InvalidArgumentError(GenerateFormattedError(
931-
packet_description, absl::StrJoin(invalid_reasons, "\n")));
937+
packet_description(), absl::StrJoin(invalid_reasons, "\n")));
932938
}
933939

934940
return result;

‎p4_pdpi/ir_utils.cc‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,16 @@ absl::StatusOr<IrValue> ArbitraryByteStringToIrValue(Format format,
230230
case Format::HEX_STRING: {
231231
ASSIGN_OR_RETURN(std::string normalized_bytes,
232232
ArbitraryToNormalizedByteString(bytes, bitwidth));
233-
std::string hex_string = absl::BytesToHexString(normalized_bytes);
234233
const int expected_num_hex_chars =
235234
bitwidth / 4 + (bitwidth % 4 != 0 ? 1 : 0);
236-
if (expected_num_hex_chars != hex_string.size()) {
235+
const std::string hex_string = absl::BytesToHexString(normalized_bytes);
236+
absl::string_view hex_view = hex_string;
237+
if (expected_num_hex_chars != hex_view.size()) {
237238
// absl::BytesToHexString operates on bytes (= 8 bits), but we want to
238239
// operate on nibbles (= 4 bits). This fixes the length as necessary.
239-
hex_string = hex_string.substr(1);
240+
hex_view.remove_prefix(1);
240241
}
241-
result.set_hex_str(absl::StrCat("0x", hex_string));
242+
result.set_hex_str(absl::StrCat("0x", hex_view));
242243
return result;
243244
}
244245
default:

‎p4_pdpi/pd.cc‎

Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ absl::Status SetStringField(google::protobuf::Message* message,
351351
GetFieldDescriptor(*message, fieldname));
352352
RETURN_IF_ERROR(ValidateFieldDescriptorType(field_descriptor,
353353
FieldDescriptor::TYPE_STRING));
354-
message->GetReflection()->SetString(message, field_descriptor, value);
354+
message->GetReflection()->SetString(message, field_descriptor,
355+
std::move(value));
355356
return absl::OkStatus();
356357
}
357358

@@ -361,7 +362,8 @@ absl::Status SetBytesField(google::protobuf::Message* message,
361362
GetFieldDescriptor(*message, fieldname));
362363
RETURN_IF_ERROR(ValidateFieldDescriptorType(field_descriptor,
363364
FieldDescriptor::TYPE_BYTES));
364-
message->GetReflection()->SetString(message, field_descriptor, value);
365+
message->GetReflection()->SetString(message, field_descriptor,
366+
std::move(value));
365367
return absl::OkStatus();
366368
}
367369

@@ -631,20 +633,32 @@ absl::Status IrTableEntriesToPd(const IrP4Info& ir_p4info,
631633
return absl::OkStatus();
632634
}
633635

636+
// Defined below. See definition for details.
637+
template <typename T>
638+
absl::Status IrPacketIoToPd(const IrP4Info& info, absl::string_view kind,
639+
const T& packet,
640+
google::protobuf::Message* pd_packet,
641+
const TranslationOptions& options,
642+
std::string* movable_payload = nullptr);
643+
634644
absl::Status PiPacketInToPd(const IrP4Info& info,
635645
const p4::v1::PacketIn& pi_packet,
636646
google::protobuf::Message* pd_packet,
637647
const TranslationOptions& options) {
638-
ASSIGN_OR_RETURN(const auto ir, PiPacketInToIr(info, pi_packet, options));
639-
return IrPacketInToPd(info, ir, pd_packet, options);
648+
ASSIGN_OR_RETURN(IrPacketIn ir, PiPacketInToIr(info, pi_packet, options));
649+
// `ir` is a temporary, so its payload can be moved rather than copied.
650+
return IrPacketIoToPd<IrPacketIn>(info, "packet-in", ir, pd_packet, options,
651+
ir.mutable_payload());
640652
}
641653

642654
absl::Status PiPacketOutToPd(const IrP4Info& info,
643655
const p4::v1::PacketOut& pi_packet,
644656
google::protobuf::Message* pd_packet,
645657
const TranslationOptions& options) {
646-
ASSIGN_OR_RETURN(const auto ir, PiPacketOutToIr(info, pi_packet, options));
647-
return IrPacketOutToPd(info, ir, pd_packet, options);
658+
ASSIGN_OR_RETURN(IrPacketOut ir, PiPacketOutToIr(info, pi_packet, options));
659+
// `ir` is a temporary, so its payload can be moved rather than copied.
660+
return IrPacketIoToPd<IrPacketOut>(info, "packet-out", ir, pd_packet, options,
661+
ir.mutable_payload());
648662
}
649663

650664
absl::Status PiReadRequestToPd(const IrP4Info& info,
@@ -1325,54 +1339,74 @@ absl::Status IrTableEntryToPd(const IrP4Info& ir_p4info, const IrTableEntry& ir,
13251339
return absl::OkStatus();
13261340
}
13271341

1342+
// Generic helper for packet-in and packet-out. If `movable_payload` is
1343+
// non-null, it must point to `packet.payload()` and its contents are moved into
1344+
// `pd_packet` instead of being copied.
1345+
//
1346+
// This function is on the PacketIn/PacketOut hot path, so it avoids copying
1347+
// the metadata definitions and only formats strings on the error path.
13281348
template <typename T>
1329-
absl::Status IrPacketIoToPd(const IrP4Info& info, const std::string& kind,
1349+
absl::Status IrPacketIoToPd(const IrP4Info& info, absl::string_view kind,
13301350
const T& packet,
13311351
google::protobuf::Message* pd_packet,
1332-
const TranslationOptions& options) {
1333-
const std::string& packet_description = absl::StrCat("'", kind, "' message");
1334-
const auto& field_descriptor = GetFieldDescriptor(*pd_packet, "payload");
1335-
if (!field_descriptor.ok()) {
1352+
const TranslationOptions& options,
1353+
std::string* movable_payload) {
1354+
auto packet_description = [kind] {
1355+
return absl::StrCat("'", kind, "' message");
1356+
};
1357+
const google::protobuf::Map<std::string, IrPacketIoMetadataDefinition>*
1358+
metadata_by_name = nullptr;
1359+
if (kind == "packet-in") {
1360+
metadata_by_name = &info.packet_in_metadata_by_name();
1361+
} else if (kind == "packet-out") {
1362+
metadata_by_name = &info.packet_out_metadata_by_name();
1363+
} else {
13361364
return absl::InvalidArgumentError(GenerateFormattedError(
1337-
packet_description,
1338-
absl::StrCat(kNewBullet, field_descriptor.status().message())));
1365+
packet_description(),
1366+
absl::StrCat(kNewBullet, "Invalid PacketIo type.")));
13391367
}
1340-
const auto& validate_status = ValidateFieldDescriptorType(
1341-
*field_descriptor, FieldDescriptor::TYPE_BYTES);
1368+
1369+
const google::protobuf::Descriptor* pd_descriptor =
1370+
pd_packet->GetDescriptor();
1371+
const google::protobuf::Reflection* pd_reflection =
1372+
pd_packet->GetReflection();
1373+
const FieldDescriptor* payload_field =
1374+
pd_descriptor->FindFieldByName("payload");
1375+
if (payload_field == nullptr) {
1376+
return absl::InvalidArgumentError(GenerateFormattedError(
1377+
packet_description(),
1378+
absl::StrCat(kNewBullet, "Field payload missing in ",
1379+
pd_packet->GetTypeName())));
1380+
}
1381+
const auto& validate_status =
1382+
ValidateFieldDescriptorType(payload_field, FieldDescriptor::TYPE_BYTES);
13421383
if (!validate_status.ok()) {
13431384
return absl::InvalidArgumentError(GenerateFormattedError(
1344-
packet_description,
1385+
packet_description(),
13451386
absl::StrCat(kNewBullet, validate_status.message())));
13461387
}
1347-
pd_packet->GetReflection()->SetString(pd_packet, *field_descriptor,
1348-
packet.payload());
1349-
1350-
google::protobuf::Map<std::string, IrPacketIoMetadataDefinition>
1351-
metadata_by_name;
1352-
if (kind == "packet-in") {
1353-
metadata_by_name = info.packet_in_metadata_by_name();
1354-
} else if (kind == "packet-out") {
1355-
metadata_by_name = info.packet_out_metadata_by_name();
1388+
if (movable_payload != nullptr) {
1389+
pd_reflection->SetString(pd_packet, payload_field,
1390+
std::move(*movable_payload));
13561391
} else {
1357-
return absl::InvalidArgumentError(GenerateFormattedError(
1358-
packet_description,
1359-
absl::StrCat(kNewBullet, "Invalid PacketIo type.")));
1392+
pd_reflection->SetString(pd_packet, payload_field, packet.payload());
13601393
}
13611394

13621395
std::vector<std::string> invalid_reasons;
1396+
// Looked up lazily (once) so that packets without metadata leave the PD
1397+
// `metadata` field unset, as before.
1398+
absl::StatusOr<google::protobuf::Message*> pd_metadata;
1399+
bool pd_metadata_initialized = false;
13631400
for (const auto& metadata : packet.metadata()) {
13641401
const std::string& name = metadata.name();
13651402

1366-
const auto& status_or_metadata_definition =
1367-
gutil::FindPtrOrStatus(metadata_by_name, name);
1368-
if (!status_or_metadata_definition.ok()) {
1403+
const auto it = metadata_by_name->find(name);
1404+
if (it == metadata_by_name->end()) {
13691405
invalid_reasons.push_back(absl::StrCat(kNewBullet, "Metadata with name '",
13701406
name, "' not defined."));
13711407
continue;
13721408
}
1373-
1374-
const pdpi::IrPacketIoMetadataDefinition& metadata_definition =
1375-
**status_or_metadata_definition;
1409+
const pdpi::IrPacketIoMetadataDefinition& metadata_definition = it->second;
13761410

13771411
if (metadata_definition.is_padding()) {
13781412
invalid_reasons.push_back(absl::StrCat(
@@ -1382,22 +1416,17 @@ absl::Status IrPacketIoToPd(const IrP4Info& info, const std::string& kind,
13821416
continue;
13831417
}
13841418

1385-
const absl::StatusOr<std::string>& raw_value =
1386-
IrValueString(metadata.value());
1387-
if (!raw_value.ok()) {
1388-
invalid_reasons.push_back(
1389-
GenerateReason(MetadataName(name), raw_value.status().message()));
1390-
continue;
1419+
if (!pd_metadata_initialized) {
1420+
pd_metadata = GetMutableMessage(pd_packet, "metadata");
1421+
pd_metadata_initialized = true;
13911422
}
1392-
const absl::StatusOr<google::protobuf::Message*>& pd_metadata =
1393-
GetMutableMessage(pd_packet, "metadata");
13941423
if (!pd_metadata.ok()) {
13951424
invalid_reasons.push_back(
13961425
GenerateReason(MetadataName(name), pd_metadata.status().message()));
13971426
continue;
13981427
}
1399-
const absl::Status& value_status =
1400-
SetStringField(*pd_metadata, name, *raw_value);
1428+
const absl::Status value_status =
1429+
SetStringField(*pd_metadata, name, IrValueString(metadata.value()));
14011430
if (!value_status.ok()) {
14021431
invalid_reasons.push_back(
14031432
GenerateReason(MetadataName(name), value_status.message()));
@@ -1406,7 +1435,7 @@ absl::Status IrPacketIoToPd(const IrP4Info& info, const std::string& kind,
14061435
}
14071436
if (!invalid_reasons.empty()) {
14081437
return absl::InvalidArgumentError(GenerateFormattedError(
1409-
packet_description, absl::StrJoin(invalid_reasons, "\n")));
1438+
packet_description(), absl::StrJoin(invalid_reasons, "\n")));
14101439
}
14111440
return absl::OkStatus();
14121441
}

0 commit comments

Comments
 (0)