Support simd_json (#105)

This PR adds support for the simd-json library whenever decoding or encoding JSON responses. This may be enabled independently of serenity and twilight support for SIMD acceleration.

Co-authored-by: Kyle Simpson <kyleandrew.simpson@gmail.com>
This commit is contained in:
Victoria Casasampere Fernandez
2022-07-25 15:18:45 +02:00
committed by Kyle Simpson
parent 8cc7a22b0b
commit cb0a74f511
13 changed files with 60 additions and 17 deletions

View File

@@ -39,6 +39,7 @@ jobs:
- Windows - Windows
- driver only - driver only
- gateway only - gateway only
- simd json
include: include:
- name: beta - name: beta
@@ -55,6 +56,10 @@ jobs:
- name: gateway only - name: gateway only
features: serenity-rustls features: serenity-rustls
dont-test: true dont-test: true
- name: simd json
features: simd-json serenity-rustls driver gateway serenity/simd_json
rustflags: -C target-cpu=native
dont-test: true
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -94,6 +99,10 @@ jobs:
target target
key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }} key: ${{ runner.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }}
- name: Set RUSTFLAGS
if: runner.os != 'Windows'
run: echo "RUSTFLAGS=${{ matrix.rustflags || '' }}" >> $GITHUB_ENV
- name: Build all features - name: Build all features
if: matrix.features == '' if: matrix.features == ''
run: cargo build --features full-doc run: cargo build --features full-doc

View File

@@ -104,6 +104,11 @@ features = ["voice", "gateway"]
optional = true optional = true
version = "0.1" version = "0.1"
[dependencies.simd-json]
optional = true
features = ["serde_impl"]
version = "0.6.0"
[dependencies.streamcatcher] [dependencies.streamcatcher]
optional = true optional = true
version = "1" version = "1"
@@ -225,6 +230,8 @@ zlib-simd = ["twilight-gateway/zlib-simd"]
zlib-stock = ["twilight-gateway/zlib-stock"] zlib-stock = ["twilight-gateway/zlib-stock"]
serenity-deps = ["async-trait"] serenity-deps = ["async-trait"]
simdjson = []
rustls-marker = [] rustls-marker = []
native-marker = [] native-marker = []

View File

@@ -8,6 +8,12 @@ args = ["fmt", "--all"]
args = ["build", "--features", "full-doc"] args = ["build", "--features", "full-doc"]
dependencies = ["format"] dependencies = ["format"]
[tasks.build-simd]
args = ["build", "--features", "full-doc,simd-json,serenity/simd_json,twilight-gateway/simd-json"]
command = "cargo"
dependencies = ["format"]
env = { "RUSTFLAGS" = "-C target-cpu=native" }
[tasks.build-examples] [tasks.build-examples]
args = ["build", "--manifest-path", "./examples/Cargo.toml", "--workspace"] args = ["build", "--manifest-path", "./examples/Cargo.toml", "--workspace"]
command = "cargo" command = "cargo"
@@ -24,7 +30,7 @@ command = "cargo"
dependencies = ["format"] dependencies = ["format"]
[tasks.build-variants] [tasks.build-variants]
dependencies = ["build", "build-gateway", "build-driver"] dependencies = ["build", "build-gateway", "build-driver", "build-simd"]
[tasks.check] [tasks.check]
args = ["check", "--features", "full-doc"] args = ["check", "--features", "full-doc"]
@@ -37,6 +43,11 @@ dependencies = ["format"]
[tasks.test] [tasks.test]
args = ["test", "--features", "full-doc"] args = ["test", "--features", "full-doc"]
[tasks.test-simd]
args = ["test", "--features", "full-doc,simd-json,serenity/simd_json,twilight-gateway/simd-json"]
command = "cargo"
env = { "RUSTFLAGS" = "-C target-cpu=native" }
[tasks.bench] [tasks.bench]
description = "Runs performance benchmarks." description = "Runs performance benchmarks."
category = "Test" category = "Test"

View File

@@ -15,6 +15,8 @@ The library offers:
* And, by default, a fully featured voice system featuring events, queues, RT(C)P packet * And, by default, a fully featured voice system featuring events, queues, RT(C)P packet
handling, seeking on compatible streams, shared multithreaded audio stream caches, handling, seeking on compatible streams, shared multithreaded audio stream caches,
and direct Opus data passthrough from DCA files. and direct Opus data passthrough from DCA files.
* To be able to use `simd-json` from serenity, you will need to enable the `simdjson`
feature on both songbird and serenity.
## Intents ## Intents
Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent. Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.

View File

@@ -2,8 +2,12 @@
#[cfg(feature = "serenity")] #[cfg(feature = "serenity")]
use futures::channel::mpsc::TrySendError; use futures::channel::mpsc::TrySendError;
#[cfg(not(feature = "simd-json"))]
pub use serde_json::Error as JsonError;
#[cfg(feature = "serenity")] #[cfg(feature = "serenity")]
use serenity::gateway::InterMessage; use serenity::gateway::InterMessage;
#[cfg(feature = "simd-json")]
pub use simd_json::Error as JsonError;
#[cfg(feature = "gateway")] #[cfg(feature = "gateway")]
use std::{error::Error, fmt}; use std::{error::Error, fmt};
#[cfg(feature = "twilight")] #[cfg(feature = "twilight")]

View File

@@ -199,7 +199,7 @@ impl Compressed {
)?; )?;
let mut metabytes = b"DCA1\0\0\0\0".to_vec(); let mut metabytes = b"DCA1\0\0\0\0".to_vec();
let orig_len = metabytes.len(); let orig_len = metabytes.len();
serde_json::to_writer(&mut metabytes, &metadata)?; crate::json::to_writer(&mut metabytes, &metadata)?;
let meta_len = (metabytes.len() - orig_len) let meta_len = (metabytes.len() - orig_len)
.try_into() .try_into()
.map_err(|_| CodecCacheError::MetadataTooLarge)?; .map_err(|_| CodecCacheError::MetadataTooLarge)?;

View File

@@ -1,6 +1,5 @@
use crate::input::AudioStreamError; use crate::{error::JsonError, input::AudioStreamError};
use audiopus::error::Error as OpusError; use audiopus::error::Error as OpusError;
use serde_json::Error as JsonError;
use std::{ use std::{
error::Error as StdError, error::Error as StdError,
fmt::{Display, Formatter, Result as FmtResult}, fmt::{Display, Formatter, Result as FmtResult},

View File

@@ -110,9 +110,11 @@ impl FormatReader for DcaReader {
return symph_err::decode_error("missing DCA1 metadata block"); return symph_err::decode_error("missing DCA1 metadata block");
} }
let raw_json = source.read_boxed_slice_exact(size as usize)?; let mut raw_json = source.read_boxed_slice_exact(size as usize)?;
let metadata: DcaMetadata = serde_json::from_slice::<DcaMetadata>(&raw_json) // NOTE: must be mut for simd-json.
#[allow(clippy::unnecessary_mut_passed)]
let metadata: DcaMetadata = crate::json::from_slice::<DcaMetadata>(&mut raw_json)
.map_err(|_| SymphError::DecodeError("malformed DCA1 metadata block"))?; .map_err(|_| SymphError::DecodeError("malformed DCA1 metadata block"))?;
let mut revision = MetadataBuilder::new(); let mut revision = MetadataBuilder::new();

View File

@@ -1,3 +1,4 @@
use crate::error::JsonError;
use std::time::Duration; use std::time::Duration;
use symphonia_core::{meta::Metadata as ContainerMetadata, probe::ProbedMetadata}; use symphonia_core::{meta::Metadata as ContainerMetadata, probe::ProbedMetadata};
@@ -47,8 +48,8 @@ pub struct AuxMetadata {
impl AuxMetadata { impl AuxMetadata {
/// Extract metadata and details from the output of `ffprobe -of json`. /// Extract metadata and details from the output of `ffprobe -of json`.
pub fn from_ffprobe_json(value: &[u8]) -> Result<Self, serde_json::Error> { pub fn from_ffprobe_json(value: &mut [u8]) -> Result<Self, JsonError> {
let output: ffprobe::Output = serde_json::from_slice(value)?; let output: ffprobe::Output = crate::json::from_slice(value)?;
Ok(output.into_aux_metadata()) Ok(output.into_aux_metadata())
} }

View File

@@ -68,13 +68,13 @@ impl<P: AsRef<Path> + Send + Sync> Compose for File<P> {
"-i", "-i",
]; ];
let output = Command::new("ffprobe") let mut output = Command::new("ffprobe")
.args(&args) .args(&args)
.output() .output()
.await .await
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?; .map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
AuxMetadata::from_ffprobe_json(&output.stdout[..]) AuxMetadata::from_ffprobe_json(&mut output.stdout[..])
.map_err(|e| AudioStreamError::Fail(Box::new(e))) .map_err(|e| AudioStreamError::Fail(Box::new(e)))
} }
} }

View File

@@ -59,13 +59,15 @@ impl YoutubeDl {
async fn query(&mut self) -> Result<Output, AudioStreamError> { async fn query(&mut self) -> Result<Output, AudioStreamError> {
let ytdl_args = ["-j", &self.url, "-f", "ba[abr>0][vcodec=none]/best"]; let ytdl_args = ["-j", &self.url, "-f", "ba[abr>0][vcodec=none]/best"];
let output = Command::new(self.program) let mut output = Command::new(self.program)
.args(&ytdl_args) .args(&ytdl_args)
.output() .output()
.await .await
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?; .map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
let stdout: Output = serde_json::from_slice(&output.stdout[..]) // NOTE: must be mut for simd-json.
#[allow(clippy::unnecessary_mut_passed)]
let stdout: Output = crate::json::from_slice(&mut output.stdout[..])
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?; .map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
self.metadata = Some(stdout.as_aux_metadata()); self.metadata = Some(stdout.as_aux_metadata());

View File

@@ -107,6 +107,12 @@ pub use serenity_voice_model as model;
#[cfg(feature = "driver")] #[cfg(feature = "driver")]
pub use typemap_rev as typemap; pub use typemap_rev as typemap;
// Re-export serde-json APIs locally to minimise conditional config elsewhere.
#[cfg(not(feature = "simd-json"))]
pub(crate) use serde_json as json;
#[cfg(feature = "simd-json")]
pub(crate) use simd_json::serde as json;
#[cfg(feature = "driver")] #[cfg(feature = "driver")]
pub use crate::{ pub use crate::{
driver::Driver, driver::Driver,

View File

@@ -1,4 +1,4 @@
use crate::model::Event; use crate::{error::JsonError, model::Event};
use async_trait::async_trait; use async_trait::async_trait;
use async_tungstenite::{ use async_tungstenite::{
@@ -8,7 +8,6 @@ use async_tungstenite::{
WebSocketStream, WebSocketStream,
}; };
use futures::{SinkExt, StreamExt, TryStreamExt}; use futures::{SinkExt, StreamExt, TryStreamExt};
use serde_json::Error as JsonError;
use tokio::time::{timeout, Duration}; use tokio::time::{timeout, Duration};
use tracing::instrument; use tracing::instrument;
@@ -92,7 +91,7 @@ impl ReceiverExt for WsStream {
#[async_trait] #[async_trait]
impl SenderExt for SplitSink<WsStream, Message> { impl SenderExt for SplitSink<WsStream, Message> {
async fn send_json(&mut self, value: &Event) -> Result<()> { async fn send_json(&mut self, value: &Event) -> Result<()> {
Ok(serde_json::to_string(value) Ok(crate::json::to_string(value)
.map(Message::Text) .map(Message::Text)
.map_err(Error::from) .map_err(Error::from)
.map(|m| self.send(m))? .map(|m| self.send(m))?
@@ -103,7 +102,7 @@ impl SenderExt for SplitSink<WsStream, Message> {
#[async_trait] #[async_trait]
impl SenderExt for WsStream { impl SenderExt for WsStream {
async fn send_json(&mut self, value: &Event) -> Result<()> { async fn send_json(&mut self, value: &Event) -> Result<()> {
Ok(serde_json::to_string(value) Ok(crate::json::to_string(value)
.map(Message::Text) .map(Message::Text)
.map_err(Error::from) .map_err(Error::from)
.map(|m| self.send(m))? .map(|m| self.send(m))?
@@ -114,7 +113,8 @@ impl SenderExt for WsStream {
#[inline] #[inline]
pub(crate) fn convert_ws_message(message: Option<Message>) -> Result<Option<Event>> { pub(crate) fn convert_ws_message(message: Option<Message>) -> Result<Option<Event>> {
Ok(match message { Ok(match message {
Some(Message::Text(payload)) => serde_json::from_str(&payload).map(Some)?, Some(Message::Text(mut payload)) =>
crate::json::from_str(payload.as_mut_str()).map(Some)?,
Some(Message::Binary(bytes)) => { Some(Message::Binary(bytes)) => {
return Err(Error::UnexpectedBinaryMessage(bytes)); return Err(Error::UnexpectedBinaryMessage(bytes));
}, },